Edit 2 - Fixed!
The issue was fixed by correctly using absolute paths rather than relative paths, and by adding add_subdirectory to example/CMakelists.txt.
I have updated the provided code (and will leave the repository in case somebody wants to use it as a starting point.
Edit:
Adjusted CMakelists.txt files to use absolute paths only (as per recommendation from #Tsyvarev
Added exact compilation error message
Original post:
I am trying to write a library, that contains example project, and can be added as a Git submodule. The desired structure would be this:
- source
- MyLib
lib.cpp
- CMakelists.txt
- include
- MyLib
lib.h
- example
main.cpp
CMakelists.txt
CMakelists.txt (main CMake for library)
What am I trying to achieve:
The structure should probably more or less stay, so one can install the library just by adding a gitmodule
The example project should be capable of running on its own by loading the CMakelists.txt in the directory, and should be able to use the library
What is my problem:
My biggest problem is linking the example to the library which lives in a sibling folder, and make sure it compiles. I managed to write a CMakelists.txt in a way that my IDE understands #include statements correctly, but during compilation the function definitions are not found.
Could anyone provide some pointers, please?
Exact compilation error message:
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/10.3.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lMY_LIB
collect2.exe: error: ld returned 1 exit status
mingw32-make[3]: *** [CMakeFiles\MY_LIB_EXAMPLE.dir\build.make:95: MY_LIB_EXAMPLE.exe] Error 1
mingw32-make[2]: *** [CMakeFiles\Makefile2:82: CMakeFiles/MY_LIB_EXAMPLE.dir/all] Error 2
mingw32-make[1]: *** [CMakeFiles\Makefile2:89: CMakeFiles/MY_LIB_EXAMPLE.dir/rule] Error 2
mingw32-make: *** [Makefile:123: MY_LIB_EXAMPLE] Error 2
CMakelists.txt files
CMakelists.txt:
cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 20)
project(MY_LIB)
add_subdirectory(source)
source/CMakelists.txt
add_library(MY_LIB MyLib/library.cpp ../include/MyLib/library.h)
target_include_directories(MY_LIB PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/MyLib)
target_include_directories(MY_LIB PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/source/MyLib)
example/CMakelists.txt
cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 20)
#--------------------------------------------------------------------
# Get solution root
#--------------------------------------------------------------------
cmake_path(GET CMAKE_CURRENT_SOURCE_DIR PARENT_PATH LIB_PATH)
cmake_path(SET LIB_INCLUDE_PATH "${LIB_PATH}/include")
message(${LIB_PATH})
message(${LIB_INCLUDE_PATH})
#--------------------------------------------------------------------
# Set project name
#--------------------------------------------------------------------
project(MY_LIB_EXAMPLE)
#--------------------------------------------------------------------
# Add source
#--------------------------------------------------------------------
add_executable(MY_LIB_EXAMPLE main.cpp)
add_subdirectory(${LIB_PATH} library-build)
#--------------------------------------------------------------------
# Link libraries
#--------------------------------------------------------------------
target_link_libraries(MY_LIB_EXAMPLE MY_LIB)
#--------------------------------------------------------------------
# Link include directories
#--------------------------------------------------------------------
target_include_directories(MY_LIB_EXAMPLE PUBLIC ${LIB_INCLUDE_PATH})
I created a sample repository with my setup: https://github.com/jiriKralovec/cmake-library
he example project should be capable of running on its own by loading the CMakelists.txt in the directory, and should be able to use the library
Just add:
add_subdirectory(./../ some_unique_name_here)
I think I would remove source/CMakelists.txt and write it all in root CMakelists.txt. It's odd to use ../ to refer to include directories.
I suggest doing options and/or unit tests:
root CMakeLists.txt:
include(CTest)
add_library(MY_LIB ....)
# one design
if (BUILD_TESTING)
add_subdirectory(example)
endif()
# another design
add_subdirectory(utilities)
example/CMakeLists.txt:
# if it is something simple, add unit test:
add_executable(MY_LIB_EXAMPLE1 <maybe EXCLUDE_FROM_ALL?> ...)
add_test(NAME MY_LIB_EXAMPLE1 COMMAND MY_LIB_EXAMPLE1)
utilities/CMakeLists.txt:
# if it is a utility, optionally build it
add_executable(MY_LIB_UTILITY_TO_DO_SMTH ...)
option(... BUILD_MY_LIB_UTILITY_TO_DO_SMTH OFF)
if(NOT BUILD_MY_LIB_UTILITY_TO_DO_SMTH)
set_target_properties(
MY_LIB_UTILITY_TO_DO_SMTH
PROPERTIES EXCLUDE_FROM_ALL ON
)
endif()
Either way, do one CMake build. Then if user wants to build the utility, he will do cmake --build <builddir> --target MY_LIB_UTILITY_TO_DO_SMTH (or make MY_LIB_UTILITY_TO_DO_SMTH). If you would want to build unit tests, you would do cmake ... -D BUILD_TESTING=1.
Some people add all unit tests executables as EXCLUDE_FROM_ALL and make special add_custom_target(build_tests) add_target_dependencies(build_tests MY_LIB_EXAMPLE1 etc. etc.) and build that custom target before testing.
Related
My program includes a subdirectory, because a header and a source file are there which are needed. My project looks like this:
EDIT: Perhaps I didn't specify: my goal is to link frame_cap.cpp to the header grab_cut.h, as I require its functions.
[-] raspicam
| [-] main_folder
| frame_cap.cpp
| CMakeLists.txt
| [-] opencv-plus
| grab_cut.cpp
| grab_cut.h
| CMakeLists.txt
Now, I have already linked the two using the CMakeLists.txt in main_folder. It looks as follows:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(frame_cap)
SET(OpenCVPLUS_DIR "/home/pi/Desktop/raspicam/main_folder/opencv-plus")
FIND_PACKAGE( OpenCV REQUIRED )
SET(OpenCV_PACKAGE ${OpenCV_LIBRARIES})
INCLUDE_DIRECTORIES( ${OpenCV_INCLUDE_DIRS} )
INCLUDE_DIRECTORIES(${OpenCVPLUS_DIR})
LINK_DIRECTORIES(${OpenCVPLUS_DIR})
ADD_SUBDIRECTORY(${OpenCVPLUS_DIR})
ADD_EXECUTABLE( frame_cap frame_cap.cpp )
TARGET_LINK_LIBRARIES( frame_cap ${OpenCV_LIBS} ${OpenCVPLUS_DIR})
As you can see, OpenCVPLUS_DIR is, well, the opencv-plus directory, where both the header and source file are located. The CMakeLists.txt file in the opencv-plus folder looks as follows:
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(grab_cut)
SET(OpenCVPLUS_DIR "/home/pi/Desktop/raspicam/main_folder/opencv-plus")
FIND_PACKAGE(OpenCV REQUIRED)
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})
SET(SOURCES grab_cut.cpp ${OpenCVPLUS_DIR}/grab_cut.h)
ADD_EXECUTABLE(grab_cut grab_cut.cpp ${SOURCES})
TARGET_LINK_LIBRARIES(grab_cut ${OpenCV_LIBS})
Finally, this is the exact error I get:
[ 25%] Linking CXX executable frame_cap
/usr/bin/ld: cannot find -lraspicam/main_folder/opencv-plus/
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/frame_cap.dir/build.make:102: frame_cap] Error 1
make[1]: *** [CMakeFiles/Makefile2:73: CMakeFiles/frame_cap.dir/all] Error 2
make: *** [Makefile:84: all] Error 2
I'd be happy to hear your suggestions as I'm a newbie at CMake and am not really sure what I'm doing wrong.
The error:
/usr/bin/ld: cannot find -lraspicam/main_folder/opencv-plus/
occurs because you try to link a directory to your frame_cap executable here:
TARGET_LINK_LIBRARIES( frame_cap ${OpenCV_LIBS} ${OpenCVPLUS_DIR})
This doesn't make sense; only libraries should be linked to your executable.
From your comments, it sounds like you want to build two executables, but use the grab_cut functions in both. This is certainly achievable with CMake. But you appear to have a design problem, because compiling the grab_cut.cpp file with the frame_cap executable will probably cause issues, assuming both files contain a main function. You'll need to split the main function into a separate file (say, main.cpp), then you can include grab_cut.cpp in the compilation of both executables.
BTW, you can use CMAKE_CURRENT_LIST_DIR to refer to the directory of the current CMakeLists.txt file, so there is no need to list out directories in your source-tree explicitly. Also, I pray you are using a newer CMake than version 2.8. Some of the steps in your CMake files seemed unnecessary, so here are the simplified files that might work for you.
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(frame_cap)
# There is no need to spell out this directory since it exists
# in the source-tree already.
SET(OpenCVPLUS_DIR "/home/pi/Desktop/raspicam/main_folder/opencv-plus")
FIND_PACKAGE( OpenCV REQUIRED )
# Seems unused, remove it.
SET(OpenCV_PACKAGE ${OpenCV_LIBRARIES})
# Include the OpenCV and opencv-plus headers in this executable.
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS} ${CMAKE_CURRENT_LIST_DIR}/opencv-plus)
# There is no library to link from this directory, so we don't need this call.
LINK_DIRECTORIES(${OpenCVPLUS_DIR})
ADD_SUBDIRECTORY(opencv-plus)
# Add the grap_cut.cpp file to the frame_cap executable.
ADD_EXECUTABLE( frame_cap frame_cap.cpp opencv-plus/grab_cut.cpp)
# We don't link anything from opencv-plus, we compiling it directly.
TARGET_LINK_LIBRARIES( frame_cap ${OpenCV_LIBS} ${OpenCVPLUS_DIR})
And your opencv-plus/CMakeLists.txt file can be simplified to something like this:
# If this CMake file is always called as part of the top-level CMake
# file, don't need this.
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(grab_cut)
# Don't need this, as explained earlier.
SET(OpenCVPLUS_DIR "/home/pi/Desktop/raspicam/main_folder/opencv-plus")
# The top-level CMake file already found OpenCV, so the variables
# we need are already defined.
FIND_PACKAGE(OpenCV REQUIRED)
# The include directories get initialized from the parent CMake file, so no need
# for this call if the include directories are the same for both executables.
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})
# Separate 'main' function into separate file, and add it here.
SET(SOURCES grab_cut.cpp grab_cut.h main.cpp)
# Don't need to add 'grab_cut.cpp' to the executable twice! It was
# already added using the 'SOURCES' variable.
ADD_EXECUTABLE(grab_cut grab_cut.cpp ${SOURCES})
TARGET_LINK_LIBRARIES(grab_cut ${OpenCV_LIBS})
I'm trying to use cmake to install "Skeltrack" into a shared library. Here's my fork of the project: https://github.com/birgersp/Skeltrack/tree/cmake
From my understanding, target_include_directories enables a package to "save" the location of the headers it needs, so the application using my library does not have to include this directory for the library to run. Is this correct?
It seems that if my library needs some headers, I am required to include these headers in my application even though I used target_include_directories in my library...
The library's CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
project(Skeltrack)
# Set output folders
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# Find source files
file(GLOB SOURCES src/*.c)
# Include header files
include_directories(include)
# Create shared library
add_library(${PROJECT_NAME} STATIC ${SOURCES})
# Include Glib library
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
find_package(Glib REQUIRED)
target_link_libraries(${PROJECT_NAME} ${Glib_LIBRARIES})
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${Glib_INCLUDE_DIRS})
# Install library
install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME})
# Install library headers
file(GLOB HEADERS include/*.h)
install(FILES ${HEADERS} DESTINATION include/${PROJECT_NAME})
My application's CMakeListst.txt
cmake_minimum_required(VERSION 2.4.0)
project(skeltrack-test)
# Find source files
file(GLOB SOURCES src/*.cpp)
# Create executable
add_executable(${PROJECT_NAME} ${SOURCES})
# Find and link Skeltrack library
find_library(SKELTRACK Skeltrack PATH_SUFFIXES Skeltrack)
target_link_libraries(${PROJECT_NAME} ${SKELTRACK})
# Find and include Skeltrack library headers
find_path(SKELTRACK_INCDLUDE_DIRS skeltrack.h PATH_SUFFIXES Skeltrack)
target_include_directories(${PROJECT_NAME} PUBLIC ${SKELTRACK_INCDLUDE_DIRS})
Make output:
[ 50%] Building CXX object CMakeFiles/skeltrack-test.dir/src/main.o
In file included from /usr/local/include/Skeltrack/skeltrack-skeleton.h:26:0,
from /usr/local/include/Skeltrack/skeltrack.h:26,
from /home/birger/Workspace/skeltrack-test/src/main.cpp:2:
/usr/local/include/Skeltrack/skeltrack-joint.h:26:18: fatal error: glib.h: No such file or directory
compilation terminated.
CMakeFiles/skeltrack-test.dir/build.make:62: recipe for target 'CMakeFiles/skeltrack-test.dir/src/main.o' failed
make[2]: *** [CMakeFiles/skeltrack-test.dir/src/main.o] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/skeltrack-test.dir/all' failed
make[1]: *** [CMakeFiles/skeltrack-test.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
Seems I'm required to include the Glib header directory in my application, to be enable the library to run? How can I avoid having include the headers twice?
Let's check out the manual for target_include_directories to get a better idea for how it works:
target_include_directories(<target> [SYSTEM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE>
[items2...] ...])
Specify include directories to use when compiling a given target. The
named must have been created by a command such as
add_executable() or add_library() and must not be an IMPORTED target.
[...]
The INTERFACE, PUBLIC and PRIVATE keywords are required to specify the
scope of the following arguments. PRIVATE and PUBLIC items will
populate the INCLUDE_DIRECTORIES property of . PUBLIC and
INTERFACE items will populate the INTERFACE_INCLUDE_DIRECTORIES
property of . The following arguments specify include
directories.
The secret sauce lies with the INTERFACE_INCLUDE_DIRECTORIES target property:
When target dependencies are specified using target_link_libraries(),
CMake will read this property from all target dependencies to
determine the build properties of the consumer.
So, to sum up: When linking against a target, CMake will inherit all include directories from that target's INTERFACE_INCLUDE_DIRECTORIES property.
For this to work you need to ensure two things: First, your project has to link against a CMake target (because no target means no target properties) and its INTERFACE_INCLUDE_DIRECTORIES target property must expose the include directories correctly.
Now let's take a look at what you are doing:
find_library(SKELTRACK Skeltrack PATH_SUFFIXES Skeltrack)
target_link_libraries(${PROJECT_NAME} ${SKELTRACK})
Hmm... That doesn't look like a target. find_library finds files, not targets. Indeed, inspecting the value of ${SKELTRACK} will reveal that it is pointing to a filename. A check if(TARGET ${SKELTRACK}) will fail.
This is because the mechanism that you use to handle dependencies predates the interface target property system. It relies solely on file paths and thus requires you to pass all relevant options to downstream programs manually.
Unfortunately, you cannot get the more comfortable behavior for free. Someone has to create a target for Skeltrack in your application's build environment and populate its target properties accordingly. That requires CMake code. Usually it is the dependency's responsibility to provide that code (although nothing stops you from writing it yourself; it's just a bit painful to write and maintain).
CMake provides a convenient mechanism for auto-generating such code for your packages. Unfortunately, Skeltrack's build system would have to do this so that your application can benefit from it.
Since you already forked the project, why not add support for this to their build system and open a pull request?
Im trying to build a vala-application together with an own library in the same project using CMake. Building the application works fine, so I've added a new "lib" directory to my project with the following CMakeLists.txt:
# Configure precompile
vala_precompile (LIB_VALA_C ${LIB_NAME}
Session.vala
PACKAGES
gio-2.0
gee-0.8
OPTIONS
--thread
--vapidir=${CMAKE_SOURCE_DIR}/vapi
--target-glib 2.32
GENERATE_VAPI
${LIB_NAME}
GENERATE_HEADER
${CMAKE_PROJECT_NAME}
)
# Add executable
add_library (${LIB_NAME} SHARED ${LIB_VALA_C})
# Set library properties
set_target_properties (${LIB_NAME} PROPERTIES
OUTPUT_NAME ${LIB_NAME}
VERSION ${LIB_SOVERSION}.${LIB_VERSION}
SOVERSION ${LIB_SOVERSION}
)
target_link_libraries (${LIB_NAME} ${LIB_LIBRARIES})
# Installation
install (TARGETS ${LIB_NAME} DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/)
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}.pc DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig/)
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}.vapi DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/vala/vapi/)
install (FILES ${CMAKE_CURRENT_SOURCE_DIR}/${LIB_NAME}.deps DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/vala/vapi/)
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.h DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/${LIB_NAME}/)
The CMakeLists.txt in the project's root contains the following:
# Project name
project (drop)
# Minimum requirements of build-system
cmake_minimum_required (VERSION 2.8)
cmake_policy (VERSION 2.6)
# Global configuration
set (DATADIR "${CMAKE_INSTALL_PREFIX}/share")
set (PKGDATADIR "${DATADIR}/${CMAKE_PROJECT_NAME}")
set (GETTEXT_PACKAGE "${CMAKE_PROJECT_NAME}")
set (RELEASE_NAME "${CMAKE_PROJECT_NAME}")
set (VERSION "0.1")
set (VERSION_INFO "Release")
set (PREFIX ${CMAKE_INSTALL_PREFIX})
set (DOLLAR "$")
# Library configuration
set (LIB_VERSION 1.0)
set (LIB_SOVERSION 0)
set (LIB_NAME ${CMAKE_PROJECT_NAME}-${LIB_VERSION})
# Cmake-files
list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
# Configuration file
configure_file (${CMAKE_SOURCE_DIR}/dropd/config.vala.cmake ${CMAKE_SOURCE_DIR}/dropd/config.vala)
# Check for vala
find_package (Vala REQUIRED)
include (ValaVersion)
ensure_vala_version ("0.18" MINIMUM)
include (ValaPrecompile)
# Disable C compiler warnings
add_definitions (-w)
# Set gettext-package
add_definitions (-DGETTEXT_PACKAGE="${CMAKE_PROJECT_NAME}")
# Check for required dependencies
find_package (PkgConfig)
pkg_check_modules (DEPS REQUIRED granite gthread-2.0 gio-2.0 gee-0.8 avahi-gobject avahi-client)
# Link the avahi library
add_definitions (-lavahi)
# Link dependencies
add_definitions (${DEPS_CFLAGS})
add_definitions (${LIB_CFLAGS})
link_libraries (${DEPS_LIBRARIES})
link_directories (${DEPS_LIBRARY_DIRS})
link_directories (${LIB_LIBRARY_DIRS})
# Load directories
add_subdirectory (dropd)
add_subdirectory (lib)
add_subdirectory (po)
add_subdirectory (data)
add_subdirectory (schemas)
When building the project now the main application still builds without problems, but when the build of the library begins this error appears (translated):
make[2]: *** No rule to make »../lib/drop-1.0«,
required by »lib/drop-1.0«. End.
make[1]: *** [lib/CMakeFiles/drop-1.0.dir/all] Errors 2
make: *** [all] Errors 2
Badly Im not that familar with CMake and don't get what that error message means, neither what is causing it.
I have already compared my CMakeLists.txt files with them in another project that's also built out of a 'normal' application and one library, but I couldn't find the difference that makes my code not working: http://bazaar.launchpad.net/~wingpanel-devs/wingpanel/trunk/files/head:/
It would be very nice if you could give me some tips what I could have missed in the CMake-Files.
For the ones with the same problem: I've now solved the issue myself with removing the ${LIB_NAME} from the line vala_precompile (LIB_VALA_C ${LIB_NAME}. Im still not sure what's happening here, but this "solution" seemed to work.
Better ways to solve this issue are still appreciated. ;)
I recently switched the build system of my C++ project to CMake. I am trying to use the ExternalProject_Add function to download the required libraries(there are currently 3 of them, GLM and TINYOBJ are static and GLFW can be either static or dynamic) using git then link to them in my project. I want to be able to link these libraries (and possibly others) with minimal effort so that I can build on multiple platforms. Or if someone else comes in to work on the project, they won't have to worry too much about getting the correct libraries installed.
However, I keep getting these errors when building (on Windows 10 with MinGW):
[100%] Linking CXX executable app\OpenGLTest.exe
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0xd): undefined reference to `FPSCounter::getElapsedTime()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x2b): undefined reference to `FPSCounter::reset()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x54): undefined reference to `FPSCounter::setLastTick()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x5e): undefined reference to `FPSCounter::addFrame()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x12d): undefined reference to `GLCamera::getCameraZoom()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x149): undefined reference to `GLCamera::setCameraZoom(float)'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x1de): undefined reference to `GLCamera::getCameraPosition()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x747): undefined reference to `GLCamera::setCameraTarget(glm::tvec3<float, (glm::precision)0>)'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x771): undefined reference to `GLCamera::setCameraPosition(glm::tvec3<float, (glm::precision)0>)'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0x968): undefined reference to `GLRenderer_Deferred::GLRenderer_Deferred()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0xb98): undefined reference to `FPSCounter::FPSCounter()'
CMakeFiles\OpenGLTest.dir/objects.a(Main.cpp.obj):Main.cpp:(.text+0xba2): undefined reference to `FPSCounter::FPSCounter()'
collect2.exe: error: ld returned 1 exit status
CMakeFiles\OpenGLTest.dir\build.make:98: recipe for target 'app/OpenGLTest.exe' failed
mingw32-make[2]: *** [app/OpenGLTest.exe] Error 1
CMakeFiles\Makefile2:66: recipe for target 'CMakeFiles/OpenGLTest.dir/all' failed
mingw32-make[1]: *** [CMakeFiles/OpenGLTest.dir/all] Error 2
Makefile:126: recipe for target 'all' failed
mingw32-make: *** [all] Error 2
My directory structure looks like this:
|-Project
|-BUILD (all the CMake output files are here)
| |-app (this is where the .exe is output to)
| |-downloads (dependencies are downloaded here)
| |-deps
|-OpenGL (this is the source directory)
|-deps-CMakeLists.txt
|-CMakeLists.txt
|-src
|-Main.cpp
|-**Other source files and headers of the "undefined reference" errors are in this directory**
|-RenderSystem
|-More Source files
Here is my CMakeLists.txt:
cmake_minimum_required(VERSION 3.2)
project(OpenGLTest)
set(CMAKE_CXX_FLAGS "-std=c++11")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/app)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(EX_PROJ_SOURCE_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Source)
set(EX_PROJ_BUILD_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Build)
# Include OpenGL
find_package(OpenGL REQUIRED)
if (OPENGL_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
endif()
# Include GLEW
find_package(GLEW REQUIRED)
if (GLEW_FOUND)
include_directories(${GLEW_INCLUDE_DIRS})
endif()
set(GLFW_LIB_DIR ${EX_PROJ_BUILD_DIR}/GLFW_EX/src)
link_directories(${GLFW_LIB_DIR})
# Download and unpack gtest at configure time
configure_file(deps-CMakeLists.txt downloads/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
# Add gtest directly to our build
add_subdirectory(${EX_PROJ_SOURCE_DIR}/GLM_EX
${EX_PROJ_BUILD_DIR}/GLM_EX
EXCLUDE_FROM_ALL )
add_subdirectory(${EX_PROJ_SOURCE_DIR}/GLFW_EX
${EX_PROJ_BUILD_DIR}/GLFW_EX
EXCLUDE_FROM_ALL )
add_subdirectory(${EX_PROJ_SOURCE_DIR}/TINYOBJ_EX
${EX_PROJ_BUILD_DIR}/TINYOBJ_EX
EXCLUDE_FROM_ALL )
# Add the gtest include directory, since gtest
# doesn't add that dependency to its gtest target
include_directories(${EX_PROJ_SOURCE_DIR}/GLM_EX/glm
${EX_PROJ_SOURCE_DIR}/GLFW_EX/include
${EX_PROJ_SOURCE_DIR}/TINYOBJ)
# add the executable
add_executable(OpenGLTest src/Main.cpp)
target_link_libraries(OpenGLTest tinyobjloader glm glfw3 ${GLEW_LIBRARIES} ${OPENGL_LIBRARIES})
add_custom_command(TARGET OpenGLTest POST_BUILD # Adds a post-build event to MyTest
COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake - E copy_if_different..."
"${GLFW_LIB_DIR}/glfw3.dll" # <--this is in-file
$<TARGET_FILE_DIR:OpenGLTest>) # <--this is out-file path
This is the deps-CMakeLists.txt file:
cmake_minimum_required(VERSION 3.2)
project(deps-download LANGUAGES NONE)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_BASE "./deps")
# Include GLFW
ExternalProject_Add (
GLFW_EX
GIT_REPOSITORY "https://github.com/glfw/glfw.git"
GIT_TAG "master"
CMAKE_ARGS -DGLFW_BUILD_EXAMPLES=OFF
-DGLFW_BUILD_TESTS=OFF
-DGLFW_BUILD_DOCS=OFF
-DGLFW_INSTALL=OFF
-DBUILD_SHARED_LIBS=ON
UPDATE_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
# Include GLM
ExternalProject_Add (
GLM_EX
GIT_REPOSITORY "https://github.com/g-truc/glm.git"
GIT_TAG "master"
UPDATE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
# Include TINYOBJ
ExternalProject_Add (
TINYOBJ_EX
GIT_REPOSITORY "https://github.com/syoyo/tinyobjloader.git"
GIT_TAG "master"
UPDATE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")
add_dependencies(GLFW_EX GLM_EX TINYOBJ_EX)
My "main" is located in Main.cpp in the "src" directory along with all the files referenced in the errors as "undefined reference". I've added the include directories for all the libraries(right after the ExternalProject_Add command) and attempted to link the dynamic library being built for GLFW but it still doesn't seem to work.
What am I missing to get this to build correctly? Any help would be appreciated.
UPDATE:
I've shuffled some things around and moved the ExternalProject_Add commands to another file which are executed during the configure phase of my build, as suggested by Craig Scott. I've made sure that all the external libraries are linked. I even tested each library separately in a different test project to make sure the CMake files work.
All of the "undefined references" I'm getting are from files that I wrote, and are in my source tree. How/Why are they not being included?
Note: I have also tried to include the "src" directory but doesn't seem to do much of anything.
The main problem I was having was caused by not adding the source files to the final executable. I fixed the issue by adding a file(GLOB... just before the add_executable command like this:
# get all *.cpp files recursively
file(GLOB_RECURSE SRC_FILES ${PROJECT_SOURCE_DIR}/*.cpp)
# add the executable
add_executable(OpenGLTest ${SRC_FILES})
I will probably move to a solution that involves a more explicit way of adding source files in the future since GLOBs are not recommended.
Thanks to Craig Scott for your help.
While your CMakeLists.txt file does build the external projects, your OpenGLTest target doesn't link to them. Presumably, they are what provide the missing symbols your linker is complaining about. Simply adding those external projects as dependencies won't also add them to your OpenGLTest target.
To fix this problem, you need to add the external libraries to your target_link_libraries command. Unfortunately, those libraries won't exist when you run CMake (well, not the first time anyway), so you have to manually work out the details of the libraries (but see further below for an alternative). It may be enough to know just the directory in which ExternalProject will put them. It should be predictable for every build, so you can work out what it is by looking at one of your test builds. I believe you can then just add that path to the linker search path and list the base name of the library in the target_link_libraries command (the base name meaning drop any leading "lib" on unix-like platforms as well as the file suffix). If that doesn't work, you will need to construct the full path to the library and add that to the target_link_libraries command instead. This would require more work, especially if you want to build on multiple platforms (the set of CMake variables CMAKE_..._LIBRARY_PREFIX and CMAKE_..._LIBRARY_SUFFIX may be helpful here).
If manually specifying the library details bothers you and if the external projects also use CMake, there is a way to get ExternalProject to download the sources for you but then use add_subdirectory to bring them directly into your project. They would then have CMake targets you could use to specify on your target_link_libraries command and would have the added benefit of always being built with consistent compiler/linker flags as the rest of your project. The technique is discussed with Google Test as the example here:
https://crascit.com/2015/07/25/cmake-gtest/
If you wanted to, you could modify that approach to do the whole build at CMake time and then use find_library or similar, but that would make the CMake step potentially very costly and is not normally recommended.
I'm having trouble trying to link a library that I recently pushed onto Git to my CMake project.
I have named the library octal (GitHub) and am trying to link it to my other library bulletframe (GitHub). Both projects are created by me.
I anticipated that linking the library wouldn't work the first time, so I only started off with a couple of source files in octal. The folder structure of octal is as follows:
.gitignore
CMakeLists.txt
.git/
build/ # generated project files go here, but right now it's empty
src/
CMakeLists.txt
octal.h # this is empty
octal/
vector/
vector.h
vector.cpp
octal/CMakeLists.txt
#...
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_HOME_DIRECTORY}")
add_subdirectory("src")
octal/src/CMakeLists.txt
macro(add_include)
set(ALL_INCLUDES ${ALL_INCLUDES} ${ARGV})
endmacro(add_include)
add_include("octal.h")
add_include("octal/vector/vector.cpp" "octal/vector/vector.h")
add_library(${PROJECT_NAME} STATIC ${ALL_INCLUDES})
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
octal/src/vector/vector.h
#pragma once
namespace octal
{
class Vector
{
// ...
Vector Add(Vector other);
Vector Subtract(Vector other);
Vector Multiply(Vector other);
Vector Multiply(double x);
Vector Divide(Vector other);
Vector Divide(double x);
// ...
}
}
These configurations are successful in building a liboctal.a file, however I'm not sure if they are linked with bulletframe correctly. There are multiple files that use classes from octal, but this is the first error that it gives after it tries to compile the first source file, entity.cpp:
fatal error: octal/vector/vector.h: No such file or directory
#include "octal/vector/vector.h"
^
compilation terminated.
src\CMakeFiles\bulletframe.dir\build.make:57: recipe for target 'src/CMakeFiles/bulletframe.dir/entity.cpp.obj' failed
mingw32-make.exe[3]: *** [src/CMakeFiles/bulletframe.dir/entity.cpp.obj] Error 1
CMakeFiles\Makefile2:121: recipe for target 'src/CMakeFiles/bulletframe.dir/all' failed
mingw32-make.exe[2]: *** [src/CMakeFiles/bulletframe.dir/all] Error 2
CMakeFiles\Makefile2:133: recipe for target 'src/CMakeFiles/bulletframe.dir/rule' failed
mingw32-make.exe[1]: *** [src/CMakeFiles/bulletframe.dir/rule] Error 2
Makefile:130: recipe for target 'bulletframe' failed
mingw32-make.exe: *** [bulletframe] Error 2
bulletframe has a similar folder structure to octal
bulletframe/CMakeLists.txt:
include(ExternalProject)
set(EXPROJ_DIR "${CMAKE_HOME_DIRECTORY}/external")
set(LIB_DIR "${CMAKE_HOME_DIRECTORY}/lib")
set(OCTAL_NAME "octal-git")
set(OCTAL_GIT "https://github.com/TheOctopod/octal-cpp.git")
set(OCTAL_PREFIX "${EXPROJ_DIR}/${OCTAL_NAME}")
set(OCTAL_SOURCE_DIR "${EXPROJ_DIR}/${OCTAL_NAME}")
set(OCTAL_BUILD_DIR "${EXPROJ_DIR}/${OCTAL_NAME}/build")
set(OCTAL_INSTALL_DIR "${LIB_DIR}")
ExternalProject_Add(${OCTAL_NAME}
PREFIX ${OCTAL_PREFIX}
TMP_DIR ${OCTAL_PREFIX}-tmp
STAMP_DIR ${OCTAL_PREFIX}-stamp
# - Download Step ------------------
GIT_REPOSITORY ${OCTAL_GIT}
# - Update Step --------------------
UPDATE_COMMAND ""
# - Configure Step -----------------
SOURCE_DIR ${OCTAL_SOURCE_DIR}
# - Build Step ---------------------
BINARY_DIR ${OCTAL_BUILD_DIR}
# - Install Step ------------------
INSTALL_COMMAND ""
CMAKE_ARGS
"-DCMAKE_BUILD_TYPE=Release"
)
add_library("octal" STATIC IMPORTED)
set_target_properties("octal" PROPERTIES
# this is where ExternalProject built liboctal.a at
IMPORTED_LOCATION "${OCTAL_BUILD_DIR}/src"
)
add_subdirectory(src)
bulletframe/src/CMakeLists.txt
macro(add_include)
set(ALL_INCLUDES ${ALL_INCLUDES} ${ARGV})
endmacro(add_include)
add_include("entity.cpp")
add_library(${PROJECT_NAME} STATIC ${ALL_INCLUDES})
target_link_libraries(${PROJECT_NAME} "octal")
As it stands, my current configurations are unable to build bulletframe with both the Visual Studio 10 2013 and MinGW Makefiles generators.
A couple concerns come to mind as I witnessed the compiler giving similar errors every time:
Am I not building the static library (octal) correctly?
Am I not linking it to my project (bulletframe) correctly?
It looks like you're building octal OK, but aren't providing proper paths to its sources (for bulletframe to be able to #include it) nor to its compiled library.
Fixing the includes issue is simple; just add the path to the imported library's INTERFACE_INCLUDE_DIRECTORIES property. However, fixing the library's path is a little trickier.
With MSVC, the full path to the octal lib will be
${OCTAL_SOURCE_DIR}/lib/<build type>/octal.lib
where <build type> is "Debug", "Release", etc. However, with other compilers, the path will be
${OCTAL_SOURCE_DIR}/lib/liboctal.a
To get round this, I'd do something like:
add_library(octal STATIC IMPORTED)
if(MSVC)
set_target_properties(octal PROPERTIES
IMPORTED_LOCATION_DEBUG "${OCTAL_SOURCE_DIR}/lib/Debug/octal.lib"
IMPORTED_LOCATION_RELEASE "${OCTAL_SOURCE_DIR}/lib/Release/octal.lib"
INTERFACE_INCLUDE_DIRECTORIES "${OCTAL_SOURCE_DIR}/src")
else()
set_target_properties(octal PROPERTIES
IMPORTED_LOCATION "${OCTAL_SOURCE_DIR}/lib/liboctal.a"
INTERFACE_INCLUDE_DIRECTORIES "${OCTAL_SOURCE_DIR}/src")
endif()