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.GameGrid
class is a template class, I made the header include the implementation file at the end (following this StackOverflow question).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 :
make
in the integrated *PowerShell terminal.Any help would be greatly appreciated ! :)
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?
In your case, the compiler knows about the header file, because it was #include
d 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.