Search code examples
c++linuxcmakegoogletest

How do I correct my CMakeLists.txt so my project builds on both linux and windows?


I am on a project that I recently added a new feature and accompanying unit tests to. The structure of the project looks like this:

Sim
└── models
    ├── otherModels
    └── myModel
        ├── CMakeLists.txt
        ├── model.cpp
        ├── model.h
        ├── test
        │   ├── CMakeLists.txt
        │   ├── geometryTest.cpp
        │   └── geometryTest.h
        └── util
            ├── CMakeLists.txt
            ├── geometry.cpp
            ├── geometry.h
            └── probability.h

Summarize the problem

My goal is to be able to build said project and execute the unit tests both on windows and linux.

Actual Results

On Windows, I am able to build the project and run the unit tests just fine.

On linux, I get an error while building saying that the library being tested cannot be found:

/home/e40056742/projects/xxxx/build/make-Release/Sim/bin/modelTests: error while loading shared libraries: libmathUtil.so: cannot open shared object file: No such file or directory
CMake Error at /projs/xxxx/cmake-3.18.2/share/cmake-3.18/Modules/GoogleTestAddTests.cmake:77 (message):
  Error running test executable.

    Path: '/home/e40056742/projects/xxxx/build/make-Release/Sim/bin/modelTests'
    Result: 127
    Output:


Call Stack (most recent call first):
  /projs/xxxx/cmake-3.18.2/share/cmake-3.18/Modules/GoogleTestAddTests.cmake:173 (gtest_discover_tests_impl)


make[2]: *** [Sim/bin/modelTests] Error 1
make[2]: *** Deleting file `Sim/bin/modelTests'
make[1]: *** [Sim/src/models/myModel/test/CMakeFiles/modelTests.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....

Expected Results

To be able to build on linux.

What I've tried

  1. I googled how to find a file on linux and verified that the file exists:

    find . -name libmathUtil.so
    >>> ./build/make-Release/Sim/lib/libmathUtil.so
    
    
  2. I verified that everything builds on linux if I comment out add_subdirectory(test)

  3. I tried the solution posted here to a different (but I hoped related) question: How to add linker directories to cmake gtest_discover_tests

  4. I added CMake RPATH settings to my test directory's CMakeLists.txt, as suggested in another answer: https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath

  5. I tried to correctly set up the CMakeLists.txt files like so:

Top level CMake for my working directory

set( target myCoolModel )

set( sources
    model.cpp
    )

set( headers
    model.h
    )

add_library( ${target} ${sources} ${headers} )

target_link_libraries( ${target}
    ${PROJECT_LIBRARIES}
    mathUtil
    )

# Group the target library into an IDE folder
set_target_properties( ${target} PROPERTIES FOLDER ${PROJECT_FOLDER} )

# Add the utility and test subdirectory 
enable_testing()
add_subdirectory( util )
add_subdirectory( test ) 

And in the util sub-directory:

set( target mathUtil)
set( sources geometry.cpp )
set( headers geometry.h probability.h)

add_library( ${target} ${sources} ${headers} )

target_link_libraries( ${target} genMath::genMath )

target_compile_options( ${target} PRIVATE ${PROJECT_CXX_FLAGS} )

target_include_directories( ${target}
    PUBLIC  ${CMAKE_CURRENT_SOURCE_DIR} )

And the test directory:

enable_testing()

# Set target name, and dependencies
set( target modelTests )
set( sources geometryTest.cpp )
set( headers geometryTest.h)

# Find Google Test
find_package( GTest REQUIRED )

# Make test executable. 
add_executable( ${target} ${sources} ${headers} )

target_link_libraries(${target}
    mathUtil
    GTest::gtest_main
)

# Load GoogleTest and add these tests to the suite
include(GoogleTest)
gtest_discover_tests(${target})

# File unit tests into the project test folder (makes it easier to find in solution explorer)
set( PROJECT_FOLDER ${PROJECT_FOLDER}/test )
set_target_properties( ${target} PROPERTIES FOLDER ${PROJECT_FOLDER} )

Solution

  • It is likely failing because gtest_discover_tests by default actually runs the test executable at build stage to get the list of tests available. The environment, especially $LD_LIBRARY_PATH, may not be properly set up for the executable to run.

    If nothing else works, and you have CMake>=3.18, you can delay the discovery of tests (exclude from the build stage), by setting the DISCOVERY_MODE to PRE_TEST. Quote from the document:

    DISCOVERY_MODE

    New in version 3.18.

    Provides greater control over when gtest_discover_tests() performs test discovery. By default, POST_BUILD sets up a post-build command to perform test discovery at build time. In certain scenarios, like cross-compiling, this POST_BUILD behavior is not desirable. By contrast, PRE_TEST delays test discovery until just prior to test execution. This way test discovery occurs in the target environment where the test has a better chance at finding appropriate runtime dependencies.

    I actually tried to get the test discovery run in POST_BUILD mode by setting the correct environment variables using the PROPERTIES of google_discover_tests, but nothing worked and after examining the source code of GoogleTest cmake module itself I have concluded that it is not possible to set up the environment variables for the shell session where google_discover_tests runs the test executable on your behalf.

    The only other feasible alternative without using PRE_TEST is to setup the RPATH for the executable properly so that it can find the dependencies all by its own. However conventionally in CMake the RPATH should be set in the install stage rather than build stage. Also extracting the correct paths from a find_package call and then adding them to the RPATH is not always trivial and is error-prone. That's why I ended up with PRE_TEST even though I really prefer POST_BUILD if it can be done in a simple way.