Search code examples
c++cmakegoogletestcmakelists-optionscmake-language

How to correctly link modules in cpp using CMake


I'm starting to learn C++ and CMake is a bigger struggle for me. I've written a little code to get me started and also wanted to write tests from the very beginning. Except that I get Undefined Symbols every time I run my test.

I already have #include "ConfigStore.h" in test.cp and the file seems to be recongnized

Error when compiling :

[build] ld: Undefined symbols:
[build]  ConfigStore::addValue(std::__1::basic_string<char, std::__1::char_traits<char>
...
[build] clang: error: linker command failed with exit code 

Here's how I made things :

Project structure

root:

CMakeLists.txt

root/Infrastructure/ConfigStore:

ConfigStore.h /ConfigStore.cpp -> (include "../Singleton/Singleton.h")

CMakeLists.txt

root/Infrastructure/Singleton:

Singleton.h / Singleton.cpp

root/test

CMakeLists.txt googletest ConfigStore_test.cpp

To be concise, here's how I managed to add ConfigStore to my test proect.

CMakeLists (TEST) :

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/googletest/include ${CMAKE_SOURCE_DIR}/Infrastructure/ConfigStore) link_directories(${BinanceAPI_LIB_OUT}/ConfigStore)

And in CMakeLists.txt :




set(LIBRARY_OUTPUT_PATH ${BinanceAPI_LIB_OUT}/${PROJECT_NAME})
set(CONFIG_STORE_DIR ${CMAKE_CURRENT_SOURCE_DIR})

add_library(${PROJECT_NAME} SHARED ConfigStore)```

And the root CMakeLists.txt :

add_subdirectory(${CMAKE_SOURCE_DIR}/Infrastructure/ConfigStore)
add_subdirectory(test)

add_library(BinanceAPI BinanceAPI.cpp)

target_link_libraries(${PROJECT_NAME} PUBLIC 
ConfigStore)```

EDIT

Here's what's going in the .h & .cpp files :

ConfigStore.h

#ifndef CONFIGSTORE
#define CONFIGSTORE


#include "../Singleton/Singleton_H"
#include<map>
#include<fstream>
#include<string>

typedef std::map<std::string,std::string> t_Store;

class ConfigStore final : public Singleton<ConfigStore>
{
    
    
private:
 t_Store m_StoredConfig;

public:
    ConfigStore(token){ }
    ~ConfigStore() {}
    void parseFile(std::ifstream& p_inStream);
    void addValue(std::string key,std::string value);
    std::string getValue(std::string key);
};

#endif // CONFIGSTORE

ConfigStore.cpp

#include "ConfigStore.h"



void ConfigStore::addValue(std::string key, std::string value){
    auto[it,result] = m_StoredConfig.try_emplace(key,value);
    if(!result){
        //error management
    }
}

std::string ConfigStore::getValue(std::string key){
    auto pos = m_StoredConfig.find(key);
    if(pos == m_StoredConfig.end()){
        return "";
    } else {
        return pos->second;
    }

}

Singleton.h :


#ifndef SINGLETON
#define SINGLETON

template<typename T>
class Singleton
{
public:
    static T& instance();

    Singleton(const Singleton&) = delete;
    Singleton& operator= (const Singleton) = delete;

protected:
    struct token {};
    Singleton() {};
};

#endif // SINGLETON

Singleton.cpp

#include "Singleton.h"
#include <memory>


template<typename T>
T& Singleton<T>::instance(){
    static const std::unique_ptr<T> instance{new T{}};
    return *instance;

}


And finally ConfigStore_test.cpp:

#include <gtest/gtest.h>
#include "ConfigStore_H"

TEST(ConfigStoreTest, ConstructTest){
    auto &config = ConfigStore::instance();
    std::string value = "t";
    config.addValue("test",value);
    EXPECT_EQ(value,config.getValue("test"));
}

I know this is a lot of code, but I'm really struggling getting what's wrong. Thanks for helping !


Solution

  • There was two problems preventing this setup to work :

    I - Not correctly naming header & cpp files :

    All my header & cpp files were not in .h/.cpp extension, I just added _H in the end of headers. This didn't prevent Intellisense from working, but the CMake wasn't happy.

    Plus, it's better to make use of ${CMAKE_CURRENT_SOURCE_DIR} and ${CMAKE_SOURCE_DIR}, that way, you are sure there's no quiproquo about the path.

    Of course, the add_library was also updated with ${CMAKE_CURRENT_SOURCE_DIR}/ConfigStore.cpp instead of ConfigStore . This solved all the undefined symbols related to ConfigStore.

    I was left with one undefined symbol in a main.

    II - Not correctly importing Gtest:gtest_main :

    Since I'm using GTest to write my unit tests, I don't have to write a main. Instead, GTest as a gtest_main method that handles entrypoint of the tests.

    So instead of :

    target_link_libraries(
        ConfigStore_Test PUBLIC
        gtest
        ConfigStore
    )
    

    I used this :

    target_link_libraries(
        ConfigStore_Test PUBLIC
        GTest::gtest_main
        ConfigStore
    )
    

    EDIT : III - using template classes (Undefined symbol on Singleton::instance() ) :

    I had to implement the template class I've created for Singleton in the header file instead of cpp file. LINK

    which means putting the implementation of instance in the header file... Very simple workaround !

    I also deleted CMakeLists from singleton as it wasn't needed anymore. This means that I also removed Singleton from target_link_libraries...

    It's my first 48h with CMake and although I've spent more that 24h just on that problem, it was worth it. It's not very complicated !