A bit of background on what I am trying to achieve: I already have a project that I have developed in CMake (it is collection of CMake projects: basically a state machine with a few helper libraries). Now, I want to develop a GUI in Qt that this state machine can control (and make it do stuff). However, for the life of me, I cannot figure out how to properly export the library which has the Qt classes and functions to another CMake package's executable. I am on Windows, and using the Mingw-w64 compiler and make program.
I was experimenting with 2 simple CMake projects, one which has a simple Qt form (which I just copied from and modified this repository and the corresponding youtube video given in the README), and the other which has the executable. I was trying to link the first project with the second project, however, whenever I build the autogenerated makefile, it fails in the end, saying that symbols are undefined (For example undefined reference to run_stuff::run_stuff()'). I apologize if the files are dirty, I have been trying out various stuff to no avail.
Things I have tried:
I have followed the answer here very carefully.
I have tried setting CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS to ON, as suggested by many sites (like this one) for running DLLs
I have tried making the library static (hence doing away with the need for DLLs)
I have tried setting the target_include_dirs from private to public and also commenting out that line. (not sure what that does, but okay)
However, no matter what I do, I always end with linking errors in the end, and my executable in the second project is not able to access any functions from the Qt project. Any suggestions on what I should try next? Would be really helpful if you could point out errors I am making in my CMakeLists.txt files (once again apologies for the messy code and CMakeLists)
My project CMake file looks like this:
cmake_minimum_required(VERSION 3.14)
# if (WIN32)
# set(CMAKE_SHARED_LIBRARY_PREFIX "")
# endif ()
set(CMAKE_MAKE_PROGRAM $ENV{MAKE})
set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo" CACHE STRING "" FORCE)
set(CMAKE_SUPPORT_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_subdirectory(QT6CMake)
add_subdirectory(Exec)
The library CMake file:
cmake_minimum_required(VERSION 3.14)
if (WIN32)
project(MY_PROJECT LANGUAGES CXX)
elseif(UNIX)
project(MY_PROJECT)
endif()
set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo" CACHE STRING "" FORCE)
#======================= INCLUSION OF Qt =======================#
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_PREFIX_PATH $ENV{QTDIR})
find_package(Qt6Core REQUIRED)
find_package(Qt6Widgets REQUIRED)
#=================== INCLUSION OF Project Files ====================#
set(FORMS_DIR "${CMAKE_SOURCE_DIR}/forms")
set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")
set(SOURCE_DIR "${CMAKE_SOURCE_DIR}/src")
include_directories(${FORMS_DIR})
include_directories(${INCLUDE_DIR})
include_directories(${SOURCE_DIR})
file(GLOB_RECURSE SOURCES
"${FORMS_DIR}/*.ui"
"${FORMS_DIR}/*.qrc"
"${INCLUDE_DIR}/*.h"
"${SOURCE_DIR}/*.cpp"
)
#=================== SETUP EXECTUABLE ====================#
# Enable debug logging on RELWITHDEBINFO configuration
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS
$<$<CONFIG:RELWITHDEBINFO>:QT_MESSAGELOGCONTEXT>
)
# Add the forms directory to the AUTOUIC search paths
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_AUTOUIC_SEARCH_PATHS} ${FORMS_DIR})
# Add the executable
if (WIN32)
add_executable(MY_PROJECT WIN32 ${SOURCES})
elseif(UNIX)
add_executable(MY_PROJECT ${SOURCES})
endif()
# Add the target includes for MY_PROJECT
target_include_directories(MY_PROJECT PRIVATE ${FORMS_DIR})
target_include_directories(MY_PROJECT PRIVATE ${INCLUDE_DIR})
target_include_directories(MY_PROJECT PRIVATE ${SOURCE_DIR})
#===================== LINKING LIBRARIES =======================#
target_link_libraries(MY_PROJECT Qt6::Widgets)
The Exec CMake file:
cmake_minimum_required(VERSION 3.14)
project(somexec)
add_definitions(${MY_PROJECT_DEFINITIONS})
include_directories(${MY_PROJECT_INCLUDE_DIRS})
if (WIN32)
add_executable(${PROJECT_NAME} WIN32 main.cpp)
elseif(UNIX)
add_executable(${PROJECT_NAME} main.cpp)
endif()
target_link_libraries(${PROJECT_NAME} MY_PROJECT)
Here is the codebase.
EDIT: Finally the code seems to be working, courtesy of Guillaume Racicot's multiple edits and fixes. I am going to leave this repository public in case anyone wants to see the codebase. Also, right now, I don't understand all the CMake commands that he has used, will try to understand those as well
The CMake code from the tutorial uses very old fashioned and outdated CMake practices. It works when creating a simple project, but won't work when doing libraries. To share files between projects, you need to export the CMake targets. You can do that by creating this file first:
cmake/yourlibrary-config.cmake
include(CMakeFindDependencyMacro)
# write it like you find_package of your cmake scripts
find_dependency(Qt6 COMPONENTS Core Widgets REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/yourlibrary-targets.cmake")
Then, add this to your main project cmake files. You should have a CMake file that looks like this:
cmake_minimum_required(VERSION 3.21)
project(yourlibrary CXX)
# First, create your library. List all the files one by one. Don't use globs
add_library(yourlibrary src/mainwindow.cpp)
# Then add an alias of your library to enable target syntax
add_library(yourlibrary::yourlibrary ALIAS yourlibrary)
# Automatically generate Qt ui and moc
set_target_properties(yourlibrary PROPERTIES
AUTOUIC ON
AUTOMOC ON
AUTOUIC_SEARCH_PATHS forms
)
# Then, include gnuinstalldir to get the platform's standard directories:
include(GNUInstallDirs)
# Then, carefully add your include directories. All of your `target_include_directories` must look like this
target_include_directories(yourlibrary PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # include directory in your build tree
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> # include directory when installed
)
# Then, include Qt and link qt
find_package(Qt6 COMPONENTS Core Widgets REQUIRED)
target_link_libraries(yourlibrary PUBLIC Qt6::Core Qt6::Widgets)
# Now, create the install script. We install all header files under `include/yourlibrary` to install them in `<prefix>/include/yourlibrary`:
install(
DIRECTORY include/yourlibrary
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h"
)
# We add `yourlibrary` target into the export set.
# The export set will contain all targets to be imported by the other project.
# It also installs the library to the install script so they are installed:
install(TARGETS yourlibrary EXPORT yourlibrary-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Now, we install the export set. This will generate a CMake file exporting all the target for other projects to use:
install(EXPORT yourlibrary-targets
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yourlibrary"
NAMESPACE yourlibrary::
)
# Now, we also export the current buildtree. Other project will be able to import the project directly from a build dir:
configure_file(cmake/yourlibrary-config.cmake yourlibrary-config.cmake COPYONLY)
export(
EXPORT yourlibrary-targets
NAMESPACE yourlibrary::
FILE "${PROJECT_BINARY_DIR}/yourlibrary-targets.cmake"
)
# The file we created earlier:
install(
FILES cmake/yourlibrary-config.cmake
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yourlibrary"
)
I omitted the installation of the generated headers since they are usually considered private for the project.
For the library, have a source tree looking like this:
yourlibrary
├── cmake
│ └── yourlibrary-config.cmake
├── forms
│ └── mainwindow.ui
├── include
│ └── yourlibrary
│ └── mainwindow.h
├── src
│ └── mainwindow.cpp
└── CMakeLists.txt
To use the library, you have two choices.
add_subdirectory(yourlibrary)
find_package(yourlibrary REQUIRED)
You can't do both.
I usually prefer using find_package
, but it require a package manager to avoid manually building all dependencies.
add_subdirectory
Then remove find_package(yourlibrary REQUIRED)
from your project. Simply add the directory and have your main project file look like this:
cmake_minimum_required(VERSION 3.21)
project(exec LANGUAGES CXX)
# Embed the project
add_subdirectory(yourlibrary)
add_executable(myexec main.cpp)
target_link_libraries(myexec PUBLIC yourlibrary::yourlibrary)
I assume a project tree like this:
SampleProject
├── yourlibrary
│ └── ...
├── CMakeLists.txt
└── main.cpp
yourlibrary
separatelyFirst, build the library and (optionally) install it in a known location.
Now, you can build your library that uses Qt. You will need a CMakeLists.txt that looks like this:
cmake_minimum_required(VERSION 3.21)
project(myexec LANGUAGES CXX)
# also finds Qt and all
find_package(yourlibrary REQUIRED)
add_executable(myexec main.cpp)
target_link_libraries(myexec PUBLIC yourlibrary::yourlibrary)
If you install the yourlibrary
library, it will simply work. If you want to use it from its build tree, simply run the CMake command with -DCMAKE_PREFIX_PATH=/path/to/yourlibrary/build
. It should point to the build directory of the library, or the installation prefix if you installed it in a custom location.