通过 CMake 导出使用 Qt 的自定义库,以便在另一个 CMake 项目中使用 (Windows, Mingw-w64)

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

关于我要实现的目标的一些背景知识:我已经有一个用 CMake 开发的项目(它是 CMake 项目的集合:基本上是一个带有一些辅助库的状态机)。现在,我想在 Qt 中开发一个 GUI,这个状态机可以控制它(并让它做一些事情)。然而,对于我的生活,我无法弄清楚如何正确地将具有 Qt 类 和功能的库导出到另一个 CMake 包的可执行文件。我在 Windows,并使用 Mingw-w64 编译器和 make 程序。

我正在试验 2 个简单的 CMake 项目,一个有一个简单的 Qt 形式(我刚刚复制并修改 this repository 以及 README 中给出的相应 youtube 视频),另一个有可执行文件。我试图 link 第一个项目和第二个项目,但是,每当我构建自动生成的 makefile 时,它​​最终都会失败,说符号是未定义的(例如未定义对 run_stuff::run_stuff() 的引用').如果文件有问题,我深表歉意,我尝试了各种方法都无济于事。

我尝试过的事情:

但是,无论我做什么,最后总是以 linking 错误结束,我在第二个项目中的可执行文件无法访问 Qt 项目中的任何函数。关于我下一步应该尝试什么有什么建议吗?如果您能指出我在 CMakeLists.txt 文件中犯的错误,那将非常有帮助(再次为混乱的代码和 CMakeLists 道歉)

我的项目 CMake 文件如下所示:

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)

库 CMake 文件:

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)

Exec CMake 文件:

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)

这里是 codebase.

编辑: 由于 Guillaume Racicot 的多次编辑和修复,代码最终似乎可以正常工作。我将离开这个存储库 public 以防有人想查看代码库。另外,现在,我不理解他使用的所有 CMake 命令,也会尝试理解这些命令

教程中的 CMake 代码使用非常老式和过时的 CMake 实践。它在创建一个简单项目时有效,但在创建库时无效。要在项目之间共享文件,您需要导出 CMake 目标。您可以通过先创建此文件来做到这一点:

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")

然后,将其添加到您的主项目 cmake 文件中。您应该有一个如下所示的 CMake 文件:

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"
)

我省略了生成的 headers 的安装,因为它们通常被认为是项目私有的。

对于库,源代码树如下所示:

yourlibrary
├── cmake
│   └── yourlibrary-config.cmake
├── forms
│   └── mainwindow.ui
├── include
│   └── yourlibrary
│       └── mainwindow.h
├── src
│   └── mainwindow.cpp
└── CMakeLists.txt

要使用该库,您有两种选择。

  1. 您可以使用 add_subdirectory(yourlibrary)
  2. 将其嵌入到您的项目中
  3. 要么单独构建,然后使用find_package(yourlibrary REQUIRED)

你不能两者都做。

我通常更喜欢使用 find_package,但它需要一个包管理器来避免手动构建所有依赖项。


如果你使用add_subdirectory

然后从您的项目中删除 find_package(yourlibrary REQUIRED)。只需添加目录并使您的主项目文件如下所示:

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)

我假设项目树是这样的:

SampleProject
├── yourlibrary
│   └── ...
├── CMakeLists.txt
└── main.cpp

如果单独构建yourlibrary

首先,构建库并(可选)将其安装在已知位置。

现在,您可以构建使用 Qt 的库。您将需要一个如下所示的 CMakeLists.txt:

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)

如果您安装 yourlibrary 库,它就可以正常工作。如果你想从它的构建树中使用它,只需 运行 带有 -DCMAKE_PREFIX_PATH=/path/to/yourlibrary/build 的 CMake 命令。它应该指向库的构建目录,或者如果您将它安装在自定义位置,则指向安装前缀。