Tuesday, February 6, 2024

[SOLVED] Gtest with clang: undefined reference to internals

Issue

I have a tests for my project written with gtest. I use CMake for compiling project, and it works without issues with gcc:

cmake_minimum_required(VERSION 3.26)

project(cpp_stream_socket_tests)

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()

file(GLOB SRCS "src/*.cpp")
file(GLOB HEADERS "include/*.hpp")

add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE
  ${SRCS}
  PRIVATE FILE_SET HEADERS BASE_DIRS include FILES ${HEADERS}
)
set_target_properties(${PROJECT_NAME} PROPERTIES
  LINKER_LANGUAGE CXX
  CXX_STANDARD 23
  CXX_STANDARD_REQUIRED TRUE
)
target_link_libraries(${PROJECT_NAME} gtest_main cpp_stream_socket)

if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  target_compile_options(${PROJECT_NAME} PUBLIC -stdlib=libc++)
endif()

add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})

However, when I try to compile tests using clang, I get linker errors:

/usr/bin/ld: CMakeFiles/cpp_stream_socket_tests.dir/src/basic_operations.cpp.o: in function `void posix::net::tests::detail::check_res<std::__1::reference_wrapper<posix::net::socket<(posix::net::side)1, (posix::net::domain)2, (posix::net::type)1, posix::net::ipv4_address, posix::net::policy::multithread> > >(std::__1::expected<std::__1::reference_wrapper<posix::net::socket<(posix::net::side)1, (posix::net::domain)2, (posix::net::type)1, posix::net::ipv4_address, posix::net::policy::multithread> >, posix::net::error> const&)':
basic_operations.cpp:(.text._ZN5posix3net5tests6detail9check_resINSt3__117reference_wrapperINS0_6socketILNS0_4sideE1ELNS0_6domainE2ELNS0_4typeE1ENS0_12ipv4_addressENS0_6policy11multithreadEEEEEEEvRKNS4_8expectedIT_NS0_5errorEEE[_ZN5posix3net5tests6detail9check_resINSt3__117reference_wrapperINS0_6socketILNS0_4sideE1ELNS0_6domainE2ELNS0_4typeE1ENS0_12ipv4_addressENS0_6policy11multithreadEEEEEEEvRKNS4_8expectedIT_NS0_5errorEEE]+0x11f): undefined reference to `testing::internal::GetBoolAssertionFailureMessage(testing::AssertionResult const&, char const*, char const*, char const*)'
/usr/bin/ld: CMakeFiles/cpp_stream_socket_tests.dir/src/basic_operations.cpp.o: in function `testing::AssertionResult testing::internal::CmpHelperEQFailure<posix::net::state, posix::net::state>(char const*, char const*, posix::net::state const&, posix::net::state const&)':
basic_operations.cpp:(.text._ZN7testing8internal18CmpHelperEQFailureIN5posix3net5stateES4_EENS_15AssertionResultEPKcS7_RKT_RKT0_[_ZN7testing8internal18CmpHelperEQFailureIN5posix3net5stateES4_EENS_15AssertionResultEPKcS7_RKT_RKT0_]+0x7f): undefined reference to `testing::internal::EqFailure(char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool)'
/usr/bin/ld: CMakeFiles/cpp_stream_socket_tests.dir/src/basic_operations.cpp.o: in function `void testing::internal::RawBytesPrinter::PrintValue<posix::net::state, 4ul>(posix::net::state const&, std::__1::basic_ostream<char, std::__1::char_traits<char> >*)':
basic_operations.cpp:(.text._ZN7testing8internal15RawBytesPrinter10PrintValueIN5posix3net5stateELm4EEEvRKT_PNSt3__113basic_ostreamIcNS9_11char_traitsIcEEEE[_ZN7testing8internal15RawBytesPrinter10PrintValueIN5posix3net5stateELm4EEEvRKT_PNSt3__113basic_ostreamIcNS9_11char_traitsIcEEEE]+0x1e): undefined reference to `testing::internal::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::__1::basic_ostream<char, std::__1::char_traits<char> >*)'
/usr/bin/ld: CMakeFiles/cpp_stream_socket_tests.dir/src/basic_operations.cpp.o: in function `testing::AssertionResult::AppendMessage(testing::Message const&)':
basic_operations.cpp:(.text._ZN7testing15AssertionResult13AppendMessageERKNS_7MessageE[_ZN7testing15AssertionResult13AppendMessageERKNS_7MessageE]+0x73): undefined reference to `testing::Message::GetString() const'

What is missing in CMake configuration for tests? Why linker cannot find some gtest functions only when compiled with clang?

My environment:

  • gcc: 13.2.1
  • clang: 17.0.6
  • cmake: 3.28.1
  • ld: 2.41.0

Solution

As I had already pointed out in the comments, the issue is that target_compile_options(${PROJECT_NAME} PUBLIC -stdlib=libc++) only sets the standard library to libc++ for the main executable, but not the gtest and gmock libraries, which is why they're built with libstdc++. Because libc++ and libstdc++ are not ABI compatible linking the executable to the test libraries fails.

The proper way to set toolchain-related flags in CMake is to use a toolchain file. For example, you can use the following clang-toolchain.cmake file

# clang-toolchain.cmake

set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)

set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-lc++ -lc++abi")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-lc++ -lc++abi")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-lc++ -lc++abi")

Then remove all toolchain-related content from your CMakeLists.txt

# CMakeLists.txt

cmake_minimum_required(VERSION 3.26)

project(cpp_stream_socket_tests)

include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()

file(GLOB SRCS "src/*.cpp")
file(GLOB HEADERS "include/*.hpp")

add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE
  ${SRCS}
  PRIVATE FILE_SET HEADERS BASE_DIRS include FILES ${HEADERS}
)
target_link_libraries(${PROJECT_NAME} PRIVATE gtest_main cpp_stream_socket)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_23)

add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})

You can then configure and build the project with clang by calling

cmake -B build/clang -S . --toolchain clang-toolchain.cmake
cmake --build build/clang


Answered By - Corristo
Answer Checked By - Willingham (WPSolving Volunteer)