Home > Net >  Export custom library which uses Qt via CMake for use in another CMake project (Windows, Mingw-w64)
Export custom library which uses Qt via CMake for use in another CMake project (Windows, Mingw-w64)

Time:03-08

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

CodePudding user response:

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.

  1. Either you embed it in your project using add_subdirectory(yourlibrary)
  2. Either you build it separately and use 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.


If you use 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

If you build yourlibrary separately

First, 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.

  • Related