Catch2 icon indicating copy to clipboard operation
Catch2 copied to clipboard

Tests fail to link with v3.1.0 on windows when built as shared library

Open Swat-SomeBug opened this issue 2 years ago • 1 comments

Describe the bug Version v3.1.0 enables building catch2 as a shared lib but does not export/import static symbols in header files. Without proper annotations, link.exe on windows will not automatically import static variables even when they are exported by CMake. Currently Selftest cannot be built against a shared version of catch2. The following linker error is produced:

InternalBenchmark.tests.cpp.obj : error LNK2001: unresolved external symbol "private: static class Catch::IMutableContext * Catch::IMutableContext::currentContext" (?currentContext@IMutableContext@Catch@@0PEAV12@EA)
Benchmark.tests.cpp.obj : error LNK2001: unresolved external symbol "private: static class Catch::IMutableContext * Catch::IMutableContext::currentContext" (?currentContext@IMutableContext@Catch@@0PEAV12@EA)
ToStringGeneral.tests.cpp.obj : error LNK2001: unresolved external symbol "public: static int Catch::StringMaker<float,void>::precision" (?precision@?$StringMaker@MX@Catch@@2HA)
ToStringGeneral.tests.cpp.obj : error LNK2001: unresolved external symbol "public: static int Catch::StringMaker<double,void>::precision" (?precision@?$StringMaker@NX@Catch@@2HA)
tests\SelfTest.exe : fatal error LNK1120: 3 unresolved externals
ninja: build stopped: subcommand failed.

Expected behavior No linker errors when linking against a shared library build of catch2.

Reproduction steps Compile and build the project as a shared library:

mkdir build && cd build
cmake .. -DBUILD_SHARED_LIBS=ON -DCATCH_DEVELOPMENT_BUILD=ON -GNinja
cmake --build .

Platform information:

  • OS: Windows NT
  • Compiler+version: MSVC 19.30
  • Catch version: v3.1.0

Additional context CMake's WINDOWS_EXPORT_ALL_SYMBOLS exports all symbols but link.exe only auto links functions without the necessary __declspec(dllimport) annotation. Selftest relies on the above mentioned static variables that are not automatically linked even though exported.

The cleanest solution at the moment is to:

  • Create a new header file
----- catch2/export.hpp ------
#pragma once

#if defined(CATCH_STATIC)         // Using static.
#  define CATCH_SYMEXPORT
#elif defined(CATCH_STATIC_BUILD) // Building static.
#  define CATCH_SYMEXPORT
#elif defined(CATCH_SHARED)       // Using shared.
#  ifdef _WIN32
#    define CATCH_SYMEXPORT __declspec(dllimport)
#  else
#    define CATCH_SYMEXPORT
#  endif
#elif defined(CATCH_SHARED_BUILD) // Building shared.
#  ifdef _WIN32
#    define CATCH_SYMEXPORT __declspec(dllexport)
#  else
#    define CATCH_SYMEXPORT
#  endif
#else
#  define CATCH_SYMEXPORT         // Using static or shared.
//#  error define CATCH_STATIC or CATCH_SHARED preprocessor macro to signal test library type being linked
#endif
  • Include catch2/export.hpp in necessary header files (Minimum catch2/internal/catch_context.hpp and catch2/catch_tostring.hpp) as well as to src/CMakeLists.txt
  • Annotate catch2/internal/catch_context.hpp:31 with CATCH_SYMEXPORT
  • Annotate catch2/catch_tostring.hpp:299 and 305 with CATCH_SYMEXPORT
  • Add necessary compile definitions to CMake
target_compile_definitions(Catch2 PRIVATE CATCH_SHARED_BUILD INTERFACE CATCH_SHARED)

Swat-SomeBug avatar Jul 21 '22 18:07 Swat-SomeBug

This header can be generated by cmake of https://cmake.org/cmake/help/latest/module/GenerateExportHeader.html The good thing about the generated header is that if you build static library all definitions are empty so downstream users don't need to manually define CATCH_STATIC.

dimztimz avatar Jul 21 '22 18:07 dimztimz