我有一个自定义库,它导出
include/
、lib/
和 lib/cmake/
,其中包含 MyProjectConfig.cmake
,内容如下:
set(MyProject_INCLUDE_DIRS "/home/.../cmake-build-debug/thirdparty/myproj/include")
set(MyProject_LIBRARY_DIRS "/home/.../cmake-build-debug/thirdparty/myproj/lib")
set(MyProject_LIBRARIES "MyProject")
message(STATUS "MyProject found. Headers: ${MyProject_INCLUDE_DIRS}")
我在另一个项目中使用它,如下所示:
include(ExternalProject)
ExternalProject_Add(MyProjectExternal
PREFIX "${CMAKE_BINARY_DIR}/external/myproject"
GIT_REPOSITORY "[email protected]:myproject.git"
GIT_TAG "master"
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/thirdparty/myproject
)
add_dependencies(${PROJECT_NAME} MyProjectExternal)
# prevent error on first project use when dir doesn't exist
if(EXISTS ${CMAKE_BINARY_DIR}/thirdparty/myproject/lib)
find_package(MyProject REQUIRED HINTS ${CMAKE_BINARY_DIR}/thirdparty/myproject/lib/cmake)
find_library(MyProject_LIB MyProject HINTS ${MyProject_LIBRARY_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC ${MyProject_LIB})
target_include_directories(${PROJECT_NAME} PRIVATE ${MyProject_INCLUDE_DIRS})
endif()
我期望 cmake 会自动选取
MyProjectConfig.cmake
中设置的变量来按名称查找库,这样就可以工作了:
find_package(MyProject REQUIRED HINTS ${CMAKE_BINARY_DIR}/thirdparty/myproject/lib/cmake)
target_link_libraries(${PROJECT_NAME} PUBLIC MyProject)
但事实并非如此:
[ 87%] Built target MyProjectExternal
[ 90%] Linking CXX executable RootProject
/usr/bin/ld: cannot find -lMyProject
collect2: error: ld returned 1 exit status
第二部分(按此处要求)完整代码和重现问题的步骤
cmake_minimum_required(VERSION 3.8)
project(MyLib VERSION 1.0.0 LANGUAGES CXX)
add_library(${PROJECT_NAME} SHARED
src/mylib/hello.cpp
)
target_include_directories(${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
include(cmake/install.cmake)
include(GNUInstallDirs)
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
include(GenerateExportHeader)
generate_export_header(${PROJECT_NAME}
EXPORT_MACRO_NAME EXPORT
NO_EXPORT_MACRO_NAME NO_EXPORT
PREFIX_NAME MYLIB_
EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/include-exports/mylib/export.h)
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include-exports>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
install(DIRECTORY ${CMAKE_BINARY_DIR}/include-exports/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
include(CMakePackageConfigHelpers)
set_property(TARGET ${PROJECT_NAME} PROPERTY VERSION ${PROJECT_VERSION})
set_property(TARGET ${PROJECT_NAME} PROPERTY SOVERSION ${PROJECT_VERSION_MAJOR})
set_property(TARGET ${PROJECT_NAME} PROPERTY INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${PROJECT_VERSION_MAJOR})
set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY COMPATIBLE_INTERFACE_STRING ${PROJECT_VERSION_MAJOR})
write_basic_package_version_file(
"${CMAKE_BINARY_DIR}/CMakePackage/${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
export(EXPORT ${PROJECT_NAME}Targets
FILE "${CMAKE_BINARY_DIR}/CMakePackage/${PROJECT_NAME}.cmake"
)
SET(CONFIG_SOURCE_DIR ${CMAKE_SOURCE_DIR})
SET(CONFIG_DIR ${CMAKE_BINARY_DIR})
SET(${PROJECT_NAME}_INCLUDE_DIR "\${${PROJECT_NAME}_SOURCE_DIR}/include")
configure_package_config_file(${CMAKE_SOURCE_DIR}/cmake/Config.cmake.in
"${CMAKE_BINARY_DIR}/CMakePackage/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
PATH_VARS ${PROJECT_NAME}_INCLUDE_DIR)
install(EXPORT ${PROJECT_NAME}Targets
FILE ${PROJECT_NAME}.cmake
DESTINATION lib/cmake/${PROJECT_NAME}
)
install(
FILES
"${CMAKE_BINARY_DIR}/CMakePackage/${PROJECT_NAME}Config.cmake"
"${CMAKE_BINARY_DIR}/CMakePackage/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION lib/cmake/${PROJECT_NAME}
COMPONENT Devel
)
@PACKAGE_INIT@
set_and_check(@PROJECT_NAME@_INCLUDE_DIRS "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_INCLUDEDIR@")
set_and_check(@PROJECT_NAME@_LIBRARY_DIRS "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@")
set(@PROJECT_NAME@_LIBRARIES "@PROJECT_NAME@")
check_required_components(@PROJECT_NAME@)
message(STATUS "@PROJECT_NAME@ found. Headers: ${@PROJECT_NAME@_INCLUDE_DIRS}")
#ifndef MYLIB_HELLO_H
#define MYLIB_HELLO_H
#include <mylib/export.h>
namespace mylib {
extern MYLIB_EXPORT void hello();
}
#endif
#include <mylib/hello.h>
#include <iostream>
namespace mylib {
void hello() {
std::cout << "Hello, MyLib!" << std::endl;
}
}
cmake_minimum_required(VERSION 3.8)
project(MyLibConsumer VERSION 1.0.0 LANGUAGES CXX)
add_executable(${PROJECT_NAME}
main.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
include(ExternalProject)
set(LIB_INSTALL_DIR ${CMAKE_BINARY_DIR}/thirdparty/mylib)
ExternalProject_Add(MyLibExternal
PREFIX "${CMAKE_BINARY_DIR}/external/mylib"
GIT_REPOSITORY "https://github.com/...git"
GIT_TAG "mylib"
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${LIB_INSTALL_DIR}
)
add_dependencies(${PROJECT_NAME} MyLibExternal)
if(EXISTS ${LIB_INSTALL_DIR}/lib) # prevent error on first cmake load
find_package(MyLib REQUIRED HINTS ${LIB_INSTALL_DIR}/lib/cmake)
# fixme: make this work
target_link_libraries(${PROJECT_NAME} PUBLIC MyLib)
# to test: comment the above line and uncomment the below lines
#find_library(MyLib_LIB MyLib HINTS ${MyLib_LIBRARY_DIRS})
#target_link_libraries(${PROJECT_NAME} PRIVATE ${MyLib_LIB})
#target_include_directories(${PROJECT_NAME} PRIVATE ${MyLib_INCLUDE_DIRS})
endif()
#include <mylib/hello.h>
int main()
{
mylib::hello();
return 0;
}
main.cpp:1:25: fatal error: mylib/hello.h: No such file or directory
#include <mylib/hello.h>
^
使用
MyLib_*
创建的 find_package()
变量可以解决问题,但太冗长了。
按照[此处][4]的建议:
但是,推荐的方法是使用 CMake 功能来创建配置文件。这样,将创建成熟的目标,并可用于链接。
如何做到这一点?
我想分两步使用我的库,就像 Qt 一样:
find_package(Qt5Widgets)
target_link_libraries(myApp Qt5::Widgets)
只需在
MyProject.cmake
中包含
MyProjectConfig.cmake
(默认不包含)
include(${CMAKE_CURRENT_LIST_DIR}/MyProject.cmake)
关键文件最终版本:
删除了冗余变量:
*_INCLUDE_DIRS
、*_LIBRARY_DIRS
、*_LIBRARIES
@PACKAGE_INIT@
include(${CMAKE_CURRENT_LIST_DIR}/@[email protected])
check_required_components(@PROJECT_NAME@)
message(STATUS "@PROJECT_NAME@ found.")
在可执行文件之前移动外部项目,如果未构建外部项目,则
return()
。
cmake_minimum_required(VERSION 3.8)
project(MyLibConsumer VERSION 1.0.0 LANGUAGES CXX)
set(LIB_INSTALL_DIR ${CMAKE_BINARY_DIR}/thirdparty/mylib)
include(ExternalProject)
ExternalProject_Add(MyLibExternal
PREFIX "${CMAKE_BINARY_DIR}/external/mylib"
GIT_REPOSITORY "https://github.com/arteniioleg/stackoverflow-question-46772541.git"
GIT_TAG "mylib"
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${LIB_INSTALL_DIR}
)
if(NOT EXISTS ${LIB_INSTALL_DIR}/lib)
# Happens on first CMake run.
# Can't continue because the below `find_package(REQUIRED)` will fail.
# Build all external project targets then rerun CMake and build the project target.
message(AUTHOR_WARNING "Build all external projects then reload cmake.")
return()
endif()
add_executable(${PROJECT_NAME} main.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
add_dependencies(${PROJECT_NAME} MyLibExternal)
find_package(MyLib REQUIRED HINTS ${LIB_INSTALL_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE MyLib)
我期望 cmake 会自动选取
中设置的变量来按名称查找库。MyProjectConfig.cmake
find_package
命令中最“神奇”的是它如何搜索*Config.cmake
脚本。找到脚本后,它只是在调用者的上下文中执行。
在您的情况下,CMake 设置变量
MyProject_INCLUDE_DIRS
、MyProject_LIBRARY_DIRS
和 MyProject_LIBRARIES
。 仅此而已。它不会创建 MyProject 目标等等。
如果您想要
find_package()
准备与 MyProject
库链接,您需要相应地编写 MyProjectConfig.cmake
脚本(例如,从中调用 link_directories()
)。
但是,推荐的方法是使用 CMake 功能来创建配置文件。这样,将创建成熟的target,并可用于链接。有关更多信息,请参阅 cmake-packages 文档。