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.
Related
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.
I'm getting this ninja build error below while running Ninja. My CMAKE build command iscmake -G"Ninja" -DCMAKE_BUILD_TYPE=Release.
ninja: error: 'ext_deps/api/src/ext_api/build/src/lib/libapi.a', needed by 'Project', missing and no known rule to make it
Let's say my project consists of an API (downloaded via CMAKE from GitHub) and the implementation (the Project).
The layout would look like:
Project/
-- build/
-- cmake/modules
----- ExternalDep.cmake
----- FindAPI.cmake
-- CMakeLists.txt
-- src/
---- CMakeLists.txt
-- include/
Let's say that in the top-level CMakeLists.txt I do the usual business of setting build settings, CXX flags, et cetera, and then I call include(ExternalDep), which checks if the "API" library is in the user's system (if not it is downloaded via CMAKE).
In src/CMakeLists.txt I try to link against the API library using a
target_link_libraries(${PROJECT_NAME} PRIVATE ${API_LIBRARY})
The first issue I'm having is that before the "API" library can even be downloaded and built, I get the ninja build error I posted above. I'm positive the ExternalDep.cmake is included before I try to add the Project executable and link against the "API" library.
Here's a simplified version of ExternalDep.cmake:
set(EXT_DEPS_PREFIX "ext_deps")
ExternalProject_Add(ext_lib
GIT_REPOSITORY "https://github.com/fake/api.git"
GIT_TAG "master"
PREFIX "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api"
TMP_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api-tmp"
STAMP_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api-stamp"
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
SOURCE_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api"
BINARY_DIR "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api-build"
BUILD_ALWAYS true
TEST_COMMAND "")
add_dependencies(ext_projects ext_api)
set(API_LIBRARY "${CMAKE_BINARY_DIR}/${EXT_DEPS_PREFIX}/api/ext_api-build/src/lib/libapi.a")
I ran into the same issue with Ninja while it worked fine with Unix Makefiles, and I managed to get it to work with Ninja by adding a BUILD_BYPRODUCTS line to my ExternalProject_Add block. Example:
ExternalProject_Add(SDL2_PROJECT
PREFIX 3rdparty
URL https://www.libsdl.org/release/SDL2-2.0.5.tar.gz
URL_MD5 d4055424d556b4a908aa76fad63abd3c
CONFIGURE_COMMAND <SOURCE_DIR>/configure ${SDL2_configure_args} --prefix=<INSTALL_DIR> --disable-shared
INSTALL_COMMAND make install -j9
BUILD_BYPRODUCTS <INSTALL_DIR>/lib/libSDL2.a
)
ExternalProject_Get_Property(SDL2_PROJECT INSTALL_DIR)
set(SDL2_INSTALL_DIR ${INSTALL_DIR})
add_library(SDL2_LIBRARY STATIC IMPORTED GLOBAL)
set_property(TARGET SDL2_LIBRARY PROPERTY IMPORTED_LOCATION ${SDL2_INSTALL_DIR}/lib/libSDL2.a)
add_dependencies(SDL2_LIBRARY SDL2_PROJECT)
I was able to solve this by generating Unix Makefiles instead of Ninja. I'm still not exactly sure if this was the singular issue but it was definitely one of the issues.
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 have a small test program that I want to link to GLFW. I am currently able to download, configure and build the .dll using ExternalProject_Add command. When I build my test program I get an executable that doesn't run because it can't find the .dll. If I manually copy the .dll to the directory where the executable is, it runs just fine.
How do I get my executable to properly link to the library?
Is there a way to automatically copy the .dll to where it needs to be?
What is the best way to ensure that, when it comes time to package my program, the library is available to use and easily accessible?
CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (GLFW-test)
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/app )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
# Include OpenGL
find_package(OpenGL REQUIRED)
if (OPENGL_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
link_libraries(${OPENGL_LIBRARIES})
endif()
# Add directories for library linkage
set(GLFW_LIB_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Build/GLFW_EX/src)
link_directories(${GLFW_LIB_DIR})
# Download and unpack dependencies 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_subdirectory(${CMAKE_BINARY_DIR}/downloads/deps/Source/GLFW_EX
${CMAKE_BINARY_DIR}/downloads/deps/Build/GLFW_EX
EXCLUDE_FROM_ALL )
include_directories(${CMAKE_BINARY_DIR}/downloads/deps/Source/GLFW_EX/include)
add_executable(GLFW-test src/GLFW-test.cpp)
target_link_libraries (GLFW-test glfw3 ${OPENGL_LIBRARIES})
add_custom_command(TARGET GLFW-test 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:GLFW-test>) # <--this is out-file path
dep-CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (GLFW-dl)
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 ""
TEST_COMMAND "" )
UPDATE:
The way I am using ExternalProject_Add is described on this site: https://crascit.com/2015/07/25/cmake-gtest/
It allows the external projects to be configured and built only once during the configure phase of my project. I have changed the directories around a bit from their test program to make things a little easier for when I eventually add more external projects. The test project on the site does not seem to account for dynamic libraries which is what I am trying to do.
UPDATE 2:
I've added 2 set commands to help clean up the build directory towards the top of the CMakeLists file. I also added a command at the bottom which copies .dll that is built from the ExternalProject_Add command to where I need it (next to the final executable). That seems to work for Windows, but it seems a bit hacky and doesn't resolve the errors in my IDE, which is currently Eclipse. Is there still a better way to do this?
Helpful Related topics:
Setting the RPATH for external projects?
Cmake on Windows doesn't add shared library paths (works on linux)
How to copy DLL files into the same folder as the executable using CMake?
How do I get my executable to properly link to the library?
As your second link states, there is no other way than to have .dll in the same directory as executable.
Is there a way to automatically copy the .dll to where it needs to be?
In you main project you already use variable CMAKE_RUNTIME_OUTPUT_DIRECTORY for setup directory where executables and .dlls should be placed after build. You can pass this variable to ExternalProject_add for force it to use same conventions:
ExternalProject_Add (...
CMAKE_ARGS -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
...
)
What is the best way to ensure that, when it comes time to package my program, the library is available to use and easily accessible?
Packaging just uses install-tree of your project. So it is sufficient to install executables and libraries into same location:
set(INSTALL_RUNTIME_DIR bin)
install(TARGETS GLFW-test
RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR}
)
install(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/glfw3.dll
DESTINATION ${INSTALL_RUNTIME_DIR}
)
Note, that target GLFW_EX obtained from external project has no special type(like executable or library), so you need to install its deliverables using plain filenames.
I have the following situation: a Project A depends on a Project B, but both are built at the same time. Project A has to include the includes of project B and it needs also to link its libraries. Up to now I've tried this way:
ADD_SUBDIRECTORY(${CMAKE_SOURCE_DIR}/other_project other_project)
and then:
INCLUDE_DIRECTORIES(includ ${CMAKE_SOURCE_DIR}/other_project/include})
LIST(APPEND LINK_LIBS other_project)
in the CMakeLists.txt of Project A
but it doesn't seem to work, the compiler also gives me error when including the headers of Project B saying that they do not exist.
What is the right way to add dependencies in A? How should the CMakeLists.txt look like?
EDIT:
as suggested in the comments, this question was addressed in this, however I'd like to see an example of how to use it in a CMakeList.txt file.
The following is a simple example which builds zlib and then builds libxml2 which depends on the zlib we build. One thing to note, I quickly pulled this example from stuff I've done before. The libxml2 example uses make, thus will only actually build on a system which has it, e.g. Linux, Mac ...
Here is the proposed directory structure for this example ...
src/
-- CMakeLists.txt
CMake/
---- External_ZLib.cmake
---- External_libxml2.cmake
Dowloads/ ( no need to create this directory, CMake will do it for you )
build/ ( Out of source build to prevent littering source tree )
Files:
CMakeLists.txt
# Any version that supports ExternalProject should do
cmake_minimum_required( VERSION 3.1)
project(test_ext_proj)
set(test_ext_proj_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/CMake")
set(CMAKE_MODULE_PATH ${test_ext_proj_CMAKE_DIR} ${CMAKE_MODULE_PATH})
set(test_ext_proj_BUILD_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install)
set(test_ext_proj_DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Downloads CACHE PATH "Directory to store downloaded tarballs.")
include(ExternalProject)
include(External_ZLib)
include(External_libxml2)
External_ZLib.cmake:
set(ZLib_version 1.2.8)
set(ZLib_url "http://zlib.net/zlib-${ZLib_version}.tar.gz")
set(ZLib_md5 "44d667c142d7cda120332623eab69f40")
ExternalProject_Add(ZLib
URL ${ZLib_url}
URL_MD5 ${ZLib_md5}
PREFIX ${vision-tpl_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
CMAKE_GENERATOR ${gen}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${test_ext_proj_BUILD_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
)
#This variable is required so other packages can find it.
set(ZLIB_ROOT ${test_ext_proj_BUILD_INSTALL_PREFIX} CACHE PATH "" FORCE)
External_libxml2.cmake:
set(libxml2_release "2.9")
set(libxml2_patch_version 0)
set(libxml2_url "ftp://xmlsoft.org/libxml2/libxml2-sources-${libxml2_release}.${libxml2_patch_version}.tar.gz")
set(libxml2_md5 "7da7af8f62e111497d5a2b61d01bd811")
#We need to state that we're dependent on ZLib so build order is correct
set(_XML2_DEPENDS ZLib)
#This build requires make, ensure we have it, or error out.
if(CMAKE_GENERATOR MATCHES ".*Makefiles")
set(MAKE_EXECUTABLE "$(MAKE)")
else()
find_program(MAKE_EXECUTABLE make)
if(NOT MAKE_EXECUTABLE)
message(FATAL_ERROR "Could not find 'make', required to build libxml2.")
endif()
endif()
ExternalProject_Add(libxml2
DEPENDS ${_XML2_DEPENDS}
URL ${libxml2_url}
URL_MD5 ${libxml2_md5}
PREFIX ${test_ext_proj_BUILD_PREFIX}
DOWNLOAD_DIR ${test_ext_proj_DOWNLOAD_DIR}
INSTALL_DIR ${test_ext_proj_BUILD_INSTALL_PREFIX}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./configure
--prefix=${test_ext_proj_BUILD_INSTALL_PREFIX}
--with-zlib=${ZLIB_ROOT}
BUILD_COMMAND ${MAKE_EXECUTABLE}
INSTALL_COMMAND ${MAKE_EXECUTABLE} install
)