0

I'm having a little trouble when compiling a project, using Conan.io and CMake.

I'm building a small OpenGL-based project. I use an MVC architecture. I want CMake to produce two distinct .exe :

  • main.exe being a small GLFW window with a simple OpenGL context. It builds and works totally well, using conan.io to manage the libs used.
  • pentest.exe being a simple test executable which I want to use to test some basics functions from my model. This one won't be compiled when I call the make command.

Here my simplified project architecture :

├───build
│   ├───.cmake
│   │
│   ├───bin
│   │   └─── // .exe files are here
│   │
│   ├───CMakeFiles
│   │   └─── // Cmake files are here
│   │
│   └─── // ...
│
├───include
│   ├───GL
│   │   └─── GLU.h
│   │
│   └───model
│       ├───Block.hpp
│       └───GameGrid.hpp
│   
├───src
│   ├───model
│   │   ├───Block.hpp
│   │   └───GameGrid.hpp
│   │
│   ├───main.cpp
│   └───pentest.cpp
│
├───CMakeLists.txt
└───conanfile.txt

Please note that :

  • pentest.cpp doesn't rely on any external libs.
  • Even though my GameGrid class is a template class, I made the header include the implementation file at the end (following this StackOverflow question).
  • I'm very, very bad with CMake.
  • CMake command is doing very well, the errors are occuring when the make command is calling the linker for pentest.exe.

Here is my CMakeLists.txt :

cmake_minimum_required(VERSION 2.8.12)
project(TheEndless)
    
add_definitions("-std=c++17")
    
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
    
include_directories(
        ${PROJECT_SOURCE_DIR}/include
        ${PROJECT_SOURCE_DIR}/src
        ${PROJECT_SOURCE_DIR}/src/model
)
    
link_directories(${CMAKE_SOURCE_DIR}/lib)
    
add_executable(main src/main.cpp)
add_executable(pentest src/pentest.cpp)
target_link_libraries(main ${CONAN_LIBS})
target_link_libraries(pentest ${CONAN_LIBS})

Here is my pentest.cpp :

#include <iostream>
#include <string>

#include "model/Block.hpp"
#include "model/GameGrid.hpp"


int main(int argc, char const *argv[]) {
    theendless::model::Block b;
    theendless::model::GameGrid<1, 1> g;

    g(0, 0) = b;
    std::string s(g(0, 0).get_name());

    std::cout << s << std::endl;

    return 0;
}

Here is my model/Block.hpp :

#ifndef THEENDLESS_MODEL_BLOCK_HPP
#define THEENDLESS_MODEL_BLOCK_HPP


#include <string>


namespace theendless::model {
    class Block {
        private:
            std::string name;

        public:
            Block();
            Block(std::string name);

            std::string get_name() const;

            void set_name(const std::string newName);
    };
}


#endif

Here is my model/Block.cpp:

#include "model/Block.hpp"

#include <string>

namespace theendless::model {
    Block::Block() : name("default_name") {}
    Block::Block(std::string name) : name(name) {}
    
    std::string Block::get_name() const { return this->name; }

    void Block::set_name(const std::string newName) { this->name = newName; }
}

Here is the errors that are shown by make :

PS C:\projects\TheEndless\build> make
Scanning dependencies of target pentest
[ 75%] Building CXX object CMakeFiles/pentest.dir/src/pentest.cpp.obj
[100%] Linking CXX executable bin/pentest.exe
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/pentest.dir/objects.a(pentest.cpp.obj): in function `main':
C:/projects/TheEndless/src/pentest.cpp:9: undefined reference to `theendless::model::Block::Block()'
c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/projects/TheEndless/src/pentest.cpp:13: undefined reference to `theendless::model::Block::get_name[abi:cxx1c:/mingw/bin/../lib/gcc/x86_64-w64-mingw32/9.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: CMakeFiles/pentest.dir/objects.a(pentest.cpp.obj): in function `std::array<theendless::model::Block, 1ull>::array()':
c:/mingw/include/c++/9.2.0/array:94: undefined reference to `theendless::model::Block::Block()'
collect2.exe: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/pentest.dir/build.make:107: bin/pentest.exe] Error 1
make[1]: *** [CMakeFiles/Makefile2:124: CMakeFiles/pentest.dir/all] Error 2
make: *** [Makefile:103: all] Error 2

Note that including model/Block.cpp to pentest.cpp makes the problem disappear, but I kinda want to make the project be clean, so I would like not to do this.

Additional infos :

  • I'm on windows 10.
  • I'm using VSCode to edit my files, and I compile by typing make in the integrated *PowerShell terminal.
  • I'm using a Mingw distro to compile.

Any help would be greatly appreciated ! :)

  • I'm not a CMake hero myself, but the compiler doesn't seem to even compile Block. Did you try adding Block.cpp to add_executable in the CMakeLists.txt? – Tobias Brösamle Mar 17 '21 at 12:29
  • @TobiasBrösamle I just did, but it tells me that there is an `undefined reference to 'WinMain'`. My opinion is that it's because it tries to create a 'block.exe' file, and fails due to 'Block.cpp' not having any main function – PaoDerDoktor Mar 17 '21 at 13:41
  • Can you show what exactly you did? Did you add it to the already existing add_executable statement or did you create a new one? – Tobias Brösamle Mar 17 '21 at 13:43
  • In my case, it works if I add the .cpp files to add_executable(pentest ...). In this case, a visualstudio solution (I'm on Windows right now) is created which contains the .cpp files. If you also want the .hpp files in the solution, they also have to be added. – Tobias Brösamle Mar 17 '21 at 13:51
  • So, I first added a new ``add_executable(block src/model/block.cpp)`` statement. It didn't work. Then, I just read your comment and added ``src/model/block.cpp`` to the existing one for *pentest*. It still stops at linking, telling me there are multiple definitions to my ``Block::Block()`` (and others related to my ``Block`` class) methods. – PaoDerDoktor Mar 17 '21 at 15:31
  • Update : The ``multiple definitions`` error was because ``model/block.cpp`` was still included in ``pentest.cpp`` xD your trick did the job. Though, I'd like to be sure if I really do have to add every cpp file to the ``add_executable`` statements, so I'll leave the question open for a while. I feel that I'm missing something here – PaoDerDoktor Mar 17 '21 at 15:38
  • I don't think you're missing something. It's even stated in the docs for add_executable: "Adds an executable target called to be built from the source files listed in the command invocation." (source: https://cmake.org/cmake/help/latest/command/add_executable.html) – Tobias Brösamle Mar 18 '21 at 07:12

1 Answers1

0

I'm not a CMake or compiler expert, but here's how I understand what's going on.

The compiler does not search around for any headers or source files unless it is told that it has to. But when does the compiler have to search for them?

  1. The file (usually headers) are included in a file that the compiler already knows about.
  2. The file has explicitly been made known to the compiler (e.g. inside a CMakeLists.txt).

In your case, the compiler knows about the header file, because it was #included inside the pentest.cpp source file (variant 1. from above). And how did the compiler know about pentest.cpp? It was explicitly stated inside the function add_executable that the specific build target pentest is built from this file.

Now what about Block.cpp? It was not known to the compiler because it neither was included nor stated inside the CMakeLists.txt, that the compiler has to use this file. So the compiler cannot know about it.

As you already mentioned, including the .cpp file is not a good style. One huge advantage (in my opinion) of only including header files is that if the implementation of a function changes (not its declaration, but the body of the function) you don't have to recompile everything where it's used. You just have to recompile that one .cpp file.

So what's the solution? You have to make the compiler be aware of all the source files that should be used to build your target pentest. Therefore, you have to add those source files to the function add_executable. The CMake docs for add_executable tell you the following.

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

Adds an executable target called to be built from the source files listed in the command invocation.

So you have to add all the source files which shall be used for building your target to the same add_executable command invocation. In your case, the CMakeLists.txt would look like this. Note that I had to add target_compile_features and remove add_compile_definitions on my machine in order to enforce the usage of C++17.

cmake_minimum_required(VERSION 2.8.12)
project(TheEndless)
    
# add_definitions("-std=c++17") # does not work on my Windows 10 machine
    
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
    
include_directories(
        ${PROJECT_SOURCE_DIR}/include
        ${PROJECT_SOURCE_DIR}/src
        ${PROJECT_SOURCE_DIR}/src/model
)
    
link_directories(${CMAKE_SOURCE_DIR}/lib)
    
add_executable(main src/main.cpp)
# add all source files needed to build to the executable
# GameGrid.cpp is not needed because it is included in the header.
add_executable(pentest src/pentest.cpp src/model/Block.cpp)
target_link_libraries(main ${CONAN_LIBS})
target_link_libraries(pentest ${CONAN_LIBS})

target_compile_features(pentest PRIVATE cxx_std_17) # added to really use C++17, see the docs for explanation

Only "problem" I saw: Visual Studio marked Block.hpp, GameGrid.hpp, and GameGrid.cpp as "external dependencies". If you want them to be shown as part of your target, you also may add them to add_executable. I'm not sure if there is another solution as it seems to be a little bit redundant.

Tobias Brösamle
  • 598
  • 5
  • 18