Skip to content

Reconfigure drops Threads::Threads (and TBB::tbb) from PUBLIC link and installed export #90

Description

@OldCrow

Defect

Reconfiguring an existing build directory silently drops Threads::Threads (and, where TBB is in play, TBB::tbb) from the libraries' PUBLIC link and from the installed export.

detect_threading_systems() (cmake/Threading.cmake:6) early-returns when its completion flag is in the cache:

if(DEFINED CACHE{LIBSTATS_THREADING_DETECTION_COMPLETE})
    ...
    return()
endif()

Cache variables persist across configure passes; imported targets do not — they must be recreated by find_package() on every pass. On any reconfigure of an already-configured build dir, the guard skips find_package(Threads), so Threads::Threads never exists during that pass, and the consuming guard goes quiet instead of failing:

# CMakeLists.txt:603
if(TARGET Threads::Threads)
    target_link_libraries(libstats_platform_obj PRIVATE Threads::Threads)
    target_link_libraries(libstats_static PUBLIC Threads::Threads)
    target_link_libraries(libstats_shared PUBLIC Threads::Threads)
endif()

The PUBLIC links are what install(EXPORT) writes into libstats-targets.cmake — so the installed package's INTERFACE_LINK_LIBRARIES differs depending on whether the producing build dir was configured once or twice. Same install prefix, same commit, different package.

detect_tbb_unified() (cmake/Threading.cmake:140) has the identical cache guard, and CMakeLists.txt:618 the identical if(TARGET TBB::tbb) silent gate — the TBB export is lost the same way on configurations that link TBB (LIBSTATS_FORCE_TBB=ON, or platforms where TBB wins detection).

Evidence

Found 2026-07-26 during #83's install-byte-diff verification (PR #89). The baseline install tree — captured from a build dir that had been configured, then reconfigured to add -DCMAKE_EXPORT_COMPILE_COMMANDS=ON — was missing both Threads::Threads entries in libstats-targets.cmake. Fresh-configure builds of the same commit (and of the #83 branch) both produce them. The diff:

<   INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:libstats::libstats_simd_interface>"
---
>   INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:libstats::libstats_simd_interface>;Threads::Threads"

Reproduce: cmake --preset dev && cmake --preset dev -DLIBSTATS_VERBOSE_BUILD=ON (any second configure works), install both states to scratch prefixes, diff lib/cmake/libstats/libstats-targets.cmake.

Impact

  • Linux: an installed package produced from a reconfigured build dir can underlink consumers — -pthread (compile and link semantics) no longer propagates through the imported target. libstats_platform_obj also loses its PRIVATE Threads::Threads, so the library's own TUs can lose -pthread on the reconfigure pass.
  • macOS: masked — pthreads live in libSystem — which is why this survived every local build to date. The defect is still real: install content is nondeterministic in configure-count.
  • CI: not currently caught. The installed-package smoke-test leg configures exactly once, so it always sees the fresh-pass export.

Fix direction

The cheap, correct pattern: cache the detection results (as now), but make target existence the guard for the find_package() calls that create imported targets, e.g. re-run find_package(Threads QUIET) whenever NOT TARGET Threads::Threadsfind_package on a cached result is fast, and imported-target recreation is exactly what it's for. Same treatment for the TBB path. The if(TARGET ...) consumers at CMakeLists.txt:603/618 can then stay as-is, or harden to message(FATAL_ERROR) when detection said the dependency exists but the target is absent — turning any future recurrence of this class from a silent export change into a configure failure.

Verification for whoever takes this: the #83 double-configure repro above must produce byte-identical libstats-targets.cmake files, and a Linux double-configured install must let consumer_example link.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions