通过 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() 的引用').如果文件有问题,我深表歉意,我尝试了各种方法都无济于事。
我尝试过的事情:
我非常仔细地遵循了答案here。
我已经尝试将 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 设置为 ON,正如许多网站 (like this one) 对 运行 DLLs
的建议
我试过将库设为静态(因此不需要 DLL)
我试过将 target_include_dirs 从私有设置为 public 并注释掉该行。 (不确定那是做什么的,但没关系)
但是,无论我做什么,最后总是以 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
要使用该库,您有两种选择。
- 您可以使用
add_subdirectory(yourlibrary)
将其嵌入到您的项目中
- 要么单独构建,然后使用
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 命令。它应该指向库的构建目录,或者如果您将它安装在自定义位置,则指向安装前缀。
关于我要实现的目标的一些背景知识:我已经有一个用 CMake 开发的项目(它是 CMake 项目的集合:基本上是一个带有一些辅助库的状态机)。现在,我想在 Qt 中开发一个 GUI,这个状态机可以控制它(并让它做一些事情)。然而,对于我的生活,我无法弄清楚如何正确地将具有 Qt 类 和功能的库导出到另一个 CMake 包的可执行文件。我在 Windows,并使用 Mingw-w64 编译器和 make 程序。
我正在试验 2 个简单的 CMake 项目,一个有一个简单的 Qt 形式(我刚刚复制并修改 this repository 以及 README 中给出的相应 youtube 视频),另一个有可执行文件。我试图 link 第一个项目和第二个项目,但是,每当我构建自动生成的 makefile 时,它最终都会失败,说符号是未定义的(例如未定义对 run_stuff::run_stuff() 的引用').如果文件有问题,我深表歉意,我尝试了各种方法都无济于事。
我尝试过的事情:
我非常仔细地遵循了答案here。
我已经尝试将 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 设置为 ON,正如许多网站 (like this one) 对 运行 DLLs
的建议我试过将库设为静态(因此不需要 DLL)
我试过将 target_include_dirs 从私有设置为 public 并注释掉该行。 (不确定那是做什么的,但没关系)
但是,无论我做什么,最后总是以 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
要使用该库,您有两种选择。
- 您可以使用
add_subdirectory(yourlibrary)
将其嵌入到您的项目中
- 要么单独构建,然后使用
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 命令。它应该指向库的构建目录,或者如果您将它安装在自定义位置,则指向安装前缀。