Skip to content

pj_base: drop fmt from time_format.cpp (MSVC LNK2005 in consumers) - #195

Merged
facontidavide merged 1 commit into
mainfrom
fix/pj-base-no-fmt
Sep 6, 2026
Merged

pj_base: drop fmt from time_format.cpp (MSVC LNK2005 in consumers)#195
facontidavide merged 1 commit into
mainfrom
fix/pj-base-no-fmt

Conversation

@facontidavide

Copy link
Copy Markdown
Contributor

Problem

0.31.0's new pj_base/src/time_format.cpp formats via the private header-only fmt (pj_internal_fmt). Header-only mode still emits fmt's implementation symbols (vformat, format_facet<locale>::do_put, detail::is_printable, detail::write_loc, …) into time_format.o:

  • ELF (Linux/macOS): they are weak — the linker silently prefers the consumer's libfmt, so nothing failed.
  • MSVC: they are COMDATs, and a consumer that links its own fmt.lib (strong, non-COMDAT format.cc.obj) gets LNK2005 "already defined" → LNK1169. This broke pj-official-plugins PR #291's Windows build (toolbox_mosaico_plugin.dll links both pj_base.lib and fmt.lib).

The 0.31.0 "no fmt in installed exports" validation was true at the CMake-interface level; the archive contents were the gap. The released libpj_base.a carries 92 defined fmt:: symbols.

Fix

time_format.cpp only zero-pads small integers — snprintf does that. Rewrite the three format helpers with snprintf and remove the pj_internal_fmt link from pj_base entirely. Output is byte-identical (the existing 24 formatter test vectors pin it).

pj_plugins (plugin_catalog.cpp) still uses pj_internal_fmt; that archive is linked by the host, not by plugins, so it is not part of this collision surface — left as is.

Regression guard

test_sdk_install.sh Step 6: fail if the installed libpj_base.a defines any fmt:: symbol (nm -C | grep). Verified it trips on the released 0.31.0 package archive (92 matches) and passes on this branch (0).

Validation

  • ./build.sh + ./test.sh: 95/95
  • ./test_sdk_install.sh: PASSED incl. the new Step 6
  • nm -C build/pj_base/libpj_base.a | grep -c fmt:: → 0

No tag/release here — a 0.31.1 patch release is the maintainer's call; pj-official-plugins #291 then just bumps its pin.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TTJvEsHX26rG4cHmqppvQg

…consumers

Header-only fmt still emits its implementation symbols (vformat,
format_facet::do_put, detail::write_loc, …) into time_format.o. On ELF
they are weak and dedupe silently, but MSVC emits them as COMDATs that
collide (LNK2005) with the strong definitions in a consumer's own
fmt.lib — exactly what happened to toolbox_mosaico linking
plotjuggler_sdk/0.31.0 plus its own fmt on Windows.

time_format.cpp only zero-pads small integers, so format with snprintf
and drop the pj_internal_fmt link from pj_base entirely. Add an install
guard (test_sdk_install.sh Step 6) that fails if the installed
libpj_base.a defines any fmt symbol, making the Windows-only collision
visible on Linux CI: the released 0.31.0 archive trips it with 92
matches; this branch ships zero.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTJvEsHX26rG4cHmqppvQg
@facontidavide
facontidavide merged commit 6d7edc4 into main Sep 6, 2026
4 checks passed
@facontidavide
facontidavide deleted the fix/pj-base-no-fmt branch September 6, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant