因为项目目前采用的程序和库的构建方式是把所有的库和可执行程序都安装到/opt/xxx/1.0.1/bin 或者/opt/xxx/1.0.1/lib 以及/opt/xxx/1.0.1/include目录下。但这种写死的方式肯定是不太合适的,因此有必要掌握常用的其他安装方式,比如说我们安装了OpenCV等常用库,它们会安装到何处,怎么使用它们。
基于Cmake编译的C++工程常见的一种第三方库文件的使用方式是find_package()函数,在使用时还是挺方便的。但是如何构建自己的模块并安装到系统中,在独立的项目中使用find_packag()来使用这个包,相关介绍比较少(网上大多是在相同的包中构建了test程序,并没有做到独立工程中使用find_package()来引用该包)。例如,从网上使用一些别人写好的库,大概步骤是先下载下来,在库的源文件夹中使用如下命令安装:
cd <libABC_path>
mkdir build
cd build
cmake ..
make
sudo make install
通过上面的一系列命令就把这个库安装到系统里了。如果我们在写其他C程序中调用了该库,那么我们就需要在编译该程序的CMakeLists中写如:
find_package(libABC REQUIRED)
1.find_package()
如果程序中使用了外部库,事先并不知道它的头文件和链接库的位置,就要给出头文件和链接库的查找方法,并将他们链接到程序中。
find_package(<name> [major.minor] [QUIET] [NO_MODULE]
[[REQUIRED|COMPONENTS] [componets...]])
find_package()的查找路径
find_package()命令首先会在模块路径中寻找 一个事先编译好的FindXXX.cmake文件,而且一般官方给出了很多,不需要自己编写这是查找库的一个典型方式。具体查找路径依次为CMake:
模块模式
- ${CMAKE_MODULE_PATH}中的所有目录。
- 模块目录 /share/cmake-x.y/Modules/ 。
配置模式
- ~/.cmake/packages/或/usr/local/share/中的各个包目录中查找,寻找<库名字的大写>Config.cmake 或者 <库名字的小写>-config.cmake (比如库Opencv,它会查找/usr/local/share/OpenCV中的OpenCVConfig.cmake或opencv-config.cmake)。
首先是查找路径的根目录。我把几个重要的默认查找根目录总结如下:
<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH
其中,PATH
中的路径如果以bin
或sbin
结尾,则自动回退到上一级目录。找到根目录后(根目录作为下面的<prefix>),cmake会检查这些目录下的
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)
cmake找到这些目录后,会开始依次找<package>Config.cmake
或Find<package>.cmake
文件。找到后即可执行该文件并生成相关链接信息。
现在回过头来看查找路径的根目录。我认为最重要的一个是PATH
。由于/usr/bin/
在PATH
中,cmake会自动去/usr/(lib/<arch>|lib|share)/cmake/<name>*/
寻找模块(因为/usr/bin以bin结尾,所以自动回退到/usr/下作为根目录),这使得绝大部分我们直接通过apt-get
安装的库可以被找到。
另外一个比较重要的是<package>_DIR
。我们可以在调用cmake时将这个目录传给cmake。由于其优先级最高,因此cmake会优先从该目录中寻找,这样我们就可以随心所欲的配置cmake使其找到我们希望它要找到的包。而且除上述指定路径外,cmake还会直接进入<package>_DIR
下寻找。如我在3rd_parties
目录下编译了一个OpenCV(使用find_package时查找的包名是OpenCV)
,那么执行cmake时可以使用
OpenCV_DIR=../../3rd-party/opencv-3.3.4/build/ cmake ..
这样做以后,cmake会优先从该目录寻找OpenCV
。
*.cmake文件定义变量
不管使用哪一种模式,只要找到.cmake,.cmake里面都会定义下面这些变量:
<NAME>_FOUND
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDES
<NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS
<NAME>_DEFINITIONS
注意大部分包的这些变量中的包名是全大写的,如 LIBFOO_FOUND ,有些包则使用包的实际大小写,如 LibFoo_FOUND
添加头文件与链接库文件
如果找到这个包,则可以通过在工程的顶层目录中的CMakeLists.txt 文件添加 include_directories(XXX_INCLUDE_DIRS) 来包含库的头文件,添加target_link_libraries(源文件 _LIBRARIES)命令将源文件与库文件链接起来。
链接OpenCV的例子
创建t4目录添加cmake目录与main.cpp与CMakeList.txt文件
创建cmake目录添加FindOpenCV.cmake文件。
CMakeList.txt
cmake_minimum_required(VERSION 2.8)
PROJECT (HELLO)
SET(SRC_LIST main.cpp)
INCLUDE_DIRECTORIES(cmake)
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
#在${CMAKE_MODULE_PATH}中添加包含FindOpenCV.cmake目录
FIND_PACKAGE(OpenCV)
#获取OPENCV_FOUND OPENCV_INCLUDE_DIR OPENCV_LIBRARIES
INCLUDE_DIRECTORIES(${OPENCV_INCLUDE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
TARGET_LINK_LIBRARIES(hello ${OPENCV_LIBRARIES})
上述介绍了find_package的用法,以及find_package查找的路径和示例。但是怎么才能自己写一个模块,然后像OpenCV那样装到系统中给其他人用呢?
2.自定义模块
构建并安装自定义模块
首先建立一个文件夹cmake_tutorial1
,在其中建立四个文件夹src include cmake_modules build
,建立一个文件CMakeLists.txt
。src
用来放程序源文件,include
用来放头文件,cmake_modules
用来放描述模块的和安装相关的配置文件,稍后再解释。
在include中建立一个文件叫test_install.h
,该源文件内容很简单。
#include <iostream>
class Print{
public:
void PrintHelloWorld();
};
定义了一个类,拥有一个print HelloWorld的函数。自然,在src中我们实现那个函数,在src中建立一个文件test_install.cpp
,内容如下
#include <test_install.h>
void Print::PrintHelloWorld(){
std::cout<<"hello world "<<std::endl;
}
在cmake_modules
中的几个文件分别叫做FindPackage.cmake, cmake_uninstall.cmake.in, install_package.cmake, PackageConfig.cmake.in, PackageConfigVersion.cmake.in, PkgConfig.pc.in
。这些文件都是些毛啊?其实我也没有去关心他们具体的内容是什么,但是可以直接使用来他们安装我们的库,库不同也不用做什么修改,他们就好像CMake的自己的库。
FindPackage.cmake
SET( @PACKAGE_PKG_NAME@_LIBRARIES @PACKAGE_LINK_LIBS@ CACHE INTERNAL "@PACKAGE_PKG_NAME@ libraries" FORCE )
SET( @PACKAGE_PKG_NAME@_INCLUDE_DIRS @PACKAGE_INCLUDE_DIRS@ CACHE INTERNAL "@PACKAGE_PKG_NAME@ include directories" FORCE )
SET( @PACKAGE_PKG_NAME@_LIBRARY_DIRS @PACKAGE_LINK_DIRS@ CACHE INTERNAL "@PACKAGE_PKG_NAME@ library directories" FORCE )
mark_as_advanced( @PACKAGE_PKG_NAME@_LIBRARIES )
mark_as_advanced( @PACKAGE_PKG_NAME@_LIBRARY_DIRS )
mark_as_advanced( @PACKAGE_PKG_NAME@_INCLUDE_DIRS )
PackageConfig.cmake.in
SET( @PACKAGE_PKG_NAME@_LIBRARIES "@PACKAGE_LINK_LIBS@" CACHE INTERNAL "@PACKAGE_PKG_NAME@ libraries" FORCE )
SET( @PACKAGE_PKG_NAME@_INCLUDE_DIRS @PACKAGE_INCLUDE_DIRS@ CACHE INTERNAL "@PACKAGE_PKG_NAME@ include directories" FORCE )
SET( @PACKAGE_PKG_NAME@_LIBRARY_DIRS @PACKAGE_LINK_DIRS@ CACHE INTERNAL "@PACKAGE_PKG_NAME@ library directories" FORCE )
mark_as_advanced( @PACKAGE_PKG_NAME@_LIBRARIES )
mark_as_advanced( @PACKAGE_PKG_NAME@_LIBRARY_DIRS )
mark_as_advanced( @PACKAGE_PKG_NAME@_INCLUDE_DIRS )
# Compute paths
get_filename_component( PACKAGE_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH )
# This file, when used for INSTALLED code, does not use Targets... sigh.
## Library dependencies (contains definitions for IMPORTED targets)
#if(NOT TARGET "@PACKAGE_PKG_NAME@_LIBRARIES" AND NOT "@PACKAGE_PKG_NAME@_BINARY_DIR")
# include( "${PACKAGE_CMAKE_DIR}/@PACKAGE_PKG_NAME@Targets.cmake" )
# include( "${PACKAGE_CMAKE_DIR}/@PACKAGE_PKG_NAME@ConfigVersion.cmake" )
#endif()
#SET(@PACKAGE_PKG_NAME@_LIBRARIES @PACKAGE_LIBRARIES@)
#SET(@PACKAGE_PKG_NAME@_LIBRARY @PACKAGE_LIBRARY@)
#SET(@PACKAGE_PKG_NAME@_INCLUDE_DIRS @PACKAGE_INCLUDE_DIRS@)
#SET(@PACKAGE_PKG_NAME@_LINK_DIRS @PACKAGE_LINK_DIRS@)
和FindPackage.cmake基本一样,只是多了一句话get_filename_component()。主要是设置包相关的路径和变量。这2个文件分别代表了上述两种模式。
PackageConfigVersion.cmake.in
set(PACKAGE_VERSION "@PACKAGE_VERSION@")
# Check build type is valid
if( "System:${CMAKE_SYSTEM_NAME},Android:${ANDROID},iOS:${IOS}" STREQUAL
"System:@CMAKE_SYSTEM_NAME@,Android:@ANDROID@,iOS:@IOS@" )
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
else()
set(PACKAGE_VERSION_COMPATIBLE FALSE)
endif()
设置包的版本,已经实际找到的和声明的版本不一致时的处理。这个是和前面的PackageConfig.cmake.in
一样用于配置模式。
PkgConfig.pc.in
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: @PACKAGE_PKG_NAME@
Description: @PACKAGE_DESCRIPTION@
Version: @PACKAGE_VERSION@
Cflags: @PACKAGE_CFLAGS@
Libs: -L${libdir} @PACKAGE_LIBS@ @PACKAGE_LIB_LINK@
设置cmake查找的根路径为库的安装路径,设置lib目录和include目录,设置包名、描述、版本、标志等信息。这个也是配置模式用到的。
install_package.cmake
################################################################################
# install_package.cmake - This function will install and "export" your library
# or files in such a way that they can be found using either CMake's
# "FindXXX.cmake" mechanism or with pkg-config. This makes your code boradly
# compatible with traditional unix best practices, and also easy to use from
# other CMake projets.
#
# This function will create and install a ${PACKAGE}.pc pkg-config file.
# Will also create a Find${PACKAGE}.cmake, which will in turn call.
#
# install_package - Takes a package name and the following optional named arguments:
# PKG_NAME <name of the package for pkg-config>, usually the same as ${PROJECT_NAME}
# LIB_NAME <name of a library to build, if any>
# VERSION <version>
# INSTALL_HEADERS <header files to install, if any>
# DESTINATION <directory to install headers>
# INCLUDE_DIRS <list of required include directories, if any>
# LINK_LIBS <list of required link libraries, if any >
# LINK_DIRS <list of required link directories, if any>
# CFLAGS <optional list of REQUIRED c flags>
# CXXFLAGS <optional list of REQUIRED c++ flags>
#
################################################################################
include(CMakeParseArguments)
get_filename_component(modules_dir ${CMAKE_CURRENT_LIST_FILE} PATH)
function(install_package)
set(PACKAGE_OPTIONS "")
set(PACKAGE_SINGLE_ARGS "")
set( PACKAGE_MULTI_ARGS
PKG_NAME
LIB_NAME
VERSION
DESCRIPTION
INSTALL_HEADERS
INSTALL_GENERATED_HEADERS
INSTALL_HEADER_DIRS
INSTALL_INCLUDE_DIR
DESTINATION
INCLUDE_DIRS
LINK_LIBS
LINK_DIRS
CFLAGS
CXXFLAGS
)
cmake_parse_arguments( PACKAGE
"${PACKAGE_OPTIONS}"
"${PACKAGE_SINGLE_ARGS}"
"${PACKAGE_MULTI_ARGS}"
"${ARGN}"
)
# Add package to CMake package registery for use from the build tree. RISKY.
option( EXPORT_${PROJECT_NAME}
"Should the ${PROJECT_NAME} package be exported for use by other software" OFF )
mark_as_advanced( EXPORT_${PROJECT_NAME} )
# clean things up
if( PACKAGE_LINK_DIRS )
list( REMOVE_DUPLICATES PACKAGE_LINK_DIRS )
endif()
if(PACKAGE_LINK_LIBS)
list( REMOVE_DUPLICATES PACKAGE_LINK_LIBS )
endif()
if( PACKAGE_INCLUDE_DIRS)
list( REMOVE_DUPLICATES PACKAGE_INCLUDE_DIRS )
endif()
# construct Cflags arguments for pkg-config file
set( PACKAGE_CFLAGS "${PACKAGE_CFLAGS} ${CMAKE_C_FLAGS}" )
foreach(var IN LISTS PACKAGE_INCLUDE_DIRS )
set( PACKAGE_CFLAGS "${PACKAGE_CFLAGS} -I${var}" )
endforeach()
# now construct Libs.private arguments
foreach(var IN LISTS PACKAGE_LINK_DIRS )
set( PACKAGE_LIBS "${PACKAGE_LIBS} -L${var}" )
endforeach()
foreach(var IN LISTS PACKAGE_LINK_LIBS )
if( EXISTS ${var} OR ${var} MATCHES "-framework*" )
set( PACKAGE_LIBS "${PACKAGE_LIBS} ${var}" )
else() # assume it's just a -l call??
set( PACKAGE_LIBS "${PACKAGE_LIBS} -l${var}" )
endif()
endforeach()
# add any CXX flags user has passed in
if( PACKAGE_CXXFLAGS )
set( PACKAGE_CFLAGS ${PACKAGE_CXXFLAGS} )
endif()
# In case we want to install.
if( NOT EXPORT_${PROJECT_NAME} )
# add "installed" library to list of required libraries to link against
if( PACKAGE_LIB_NAME )
if(POLICY CMP0026)
cmake_policy( SET CMP0026 OLD )
endif()
get_target_property( _target_library ${PACKAGE_LIB_NAME} LOCATION )
get_filename_component( _lib ${_target_library} NAME )
list( APPEND PACKAGE_LINK_LIBS ${PACKAGE_LIB_NAME} )
endif()
if( PACKAGE_INSTALL_HEADER_DIRS )
foreach(dir IN LISTS PACKAGE_INSTALL_HEADER_DIRS )
install( DIRECTORY ${dir}
DESTINATION ${PACKAGE_DESTINATION}/include
FILES_MATCHING PATTERN "*.h|*.hxx|*.hpp"
)
endforeach()
endif()
# install header files
if( PACKAGE_INSTALL_HEADERS )
foreach(hdr IN LISTS PACKAGE_INSTALL_HEADERS )
get_filename_component( _fp ${hdr} ABSOLUTE )
file( RELATIVE_PATH _rpath ${CMAKE_SOURCE_DIR} ${_fp} )
get_filename_component( _dir ${_rpath} DIRECTORY )
install( FILES ${_fp}
DESTINATION ${PACKAGE_DESTINATION}/${_dir} )
endforeach()
endif()
if( PACKAGE_INSTALL_GENERATED_HEADERS )
foreach(hdr IN LISTS PACKAGE_INSTALL_GENERATED_HEADERS )
get_filename_component( _fp ${hdr} ABSOLUTE )
file( RELATIVE_PATH _rpath ${CMAKE_BINARY_DIR} ${_fp} )
get_filename_component( _dir ${_rpath} DIRECTORY )
install( FILES ${_fp}
DESTINATION ${PACKAGE_DESTINATION}/${_dir} )
endforeach()
endif()
if( PACKAGE_INSTALL_INCLUDE_DIR )
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include DESTINATION ${PACKAGE_DESTINATION} )
endif()
# install library itself
if( PACKAGE_LIB_NAME )
install( FILES ${_target_library} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib )
set( PACKAGE_LIB_LINK "-l${PACKAGE_LIB_NAME}" )
endif()
# build pkg-config file
if( PACKAGE_PKG_NAME )
configure_file( ${modules_dir}/PkgConfig.pc.in ${PACKAGE_PKG_NAME}.pc @ONLY )
# install pkg-config file for external projects to discover this library
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_PKG_NAME}.pc
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig/ )
#######################################################
# Export library for easy inclusion from other cmake projects. APPEND allows
# call to function even as subdirectory of larger project.
FILE(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake")
export( TARGETS ${LIBRARY_NAME}
APPEND FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake" )
# Version information. So find_package( XXX version ) will work.
configure_file( ${CMAKE_SOURCE_DIR}/cmake_modules/PackageConfigVersion.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" @ONLY )
# Build tree config. So some folks can use the built package (e.g., any of our
# own examples or applcations in this project.
configure_file( ${CMAKE_SOURCE_DIR}/cmake_modules/PackageConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake @ONLY )
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
DESTINATION
lib/cmake/${PROJECT_NAME})
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/Find${PACKAGE_PKG_NAME}.cmake
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/${PACKAGE_PKG_NAME}/ )
# # Install tree config. NB we DO NOT use this. We install using brew or
# pkg-config.
# set( EXPORT_LIB_INC_DIR ${LIB_INC_DIR} )
# set( EXPORT_LIB_INC_DIR "\${PROJECT_CMAKE_DIR}/${REL_INCLUDE_DIR}" )
# configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in
# ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake @ONLY )
endif()
# In case we want to export.
elseif( EXPORT_${PROJECT_NAME} )
if( PACKAGE_LIB_NAME )
if(POLICY CMP0026)
cmake_policy( SET CMP0026 OLD )
endif()
get_target_property( _target_library ${PACKAGE_LIB_NAME} LOCATION )
list( APPEND PACKAGE_LINK_LIBS ${_target_library} )
endif()
if( PACKAGE_INSTALL_HEADER_DIRS )
foreach(dir IN LISTS PACKAGE_INSTALL_HEADER_DIRS )
list( APPEND PACKAGE_INCLUDE_DIRS ${dir} )
endforeach()
endif()
#if( PACKAGE_INSTALL_HEADER_DIRS )
# foreach(dir IN LISTS PACKAGE_INSTALL_HEADER_DIRS )
# FILE( GLOB ${dir} "*.h" "*.hpp" )
# list( APPEND PACKAGE_INCLUDE_DIRS ${dir} )
# endforeach()
#endif()
list( APPEND PACKAGE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}
${CMAKE_BINARY_DIR} )
# install library itself
#if( PACKAGE_LIB_NAME )
# set( PACKAGE_LIB_LINK "-l${PACKAGE_LIB_NAME}" )
#endif()
#######################################################
# Export library for easy inclusion from other cmake projects. APPEND allows
# call to function even as subdirectory of larger project.
FILE(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake")
export( TARGETS ${LIBRARY_NAME}
APPEND FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake" )
export( PACKAGE ${PROJECT_NAME} )
# install( EXPORT ${PROJECT_NAME}Targets DESTINATION ${CMAKECONFIG_INSTALL_DIR} )
# install(TARGETS ${LIBRARY_NAME}
# EXPORT ${PROJECT_NAME}Targets DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
# )
# Version information. So find_package( XXX version ) will work.
configure_file( ${CMAKE_SOURCE_DIR}/cmake_modules/PackageConfigVersion.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" @ONLY )
# Build tree config. So some folks can use the built package (e.g., any of our
# own examples or applcations in this project.
configure_file( ${CMAKE_SOURCE_DIR}/cmake_modules/PackageConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake @ONLY )
# # Install tree config. NB we DO NOT use this. We install using brew or
# pkg-config.
# set( EXPORT_LIB_INC_DIR ${LIB_INC_DIR} )
# set( EXPORT_LIB_INC_DIR "\${PROJECT_CMAKE_DIR}/${REL_INCLUDE_DIR}" )
# configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in
# ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake @ONLY )
#export( PACKAGE ${PROJECT_NAME} )
endif()
# write and install a cmake "find package" for cmake projects to use.
# NB: this .cmake file CANNOT refer to any source directory, only to
# _installed_ files.
configure_file( ${modules_dir}/FindPackage.cmake.in Find${PACKAGE_PKG_NAME}.cmake @ONLY )
endfunction()
定义了一个cmake的函数,用于安装包和导出相关的配置文件(.cmake等)。
cmake_uninstall.cmake.in
## A simple uninstall script.
## Alternatively UNIX users can run/sudo `xargs rm < install_manifest.txt` in the build directory.
set(unfile ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
file(WRITE ${unfile} "IF(NOT EXISTS \"install_manifest.txt\")\n")
file(APPEND ${unfile} "MESSAGE(\"FATAL_ERROR Cannot find \\\"install manifest\\\": install_manifest.txt\")\n")
file(APPEND ${unfile} "ENDIF(NOT EXISTS \"install_manifest.txt\")\n")
file(APPEND ${unfile} "FILE(READ \"install_manifest.txt\" files)\n")
file(APPEND ${unfile} "STRING(REGEX REPLACE \"\\n\" \";\" files \"\${files}\")\n")
file(APPEND ${unfile} "FOREACH(file \${files})\n")
file(APPEND ${unfile} " MESSAGE(STATUS \"Uninstalling \\\"\${file}\\\"\")\n")
file(APPEND ${unfile} " IF(EXISTS \"\${file}\")\n")
file(APPEND ${unfile} " EXEC_PROGRAM(\n")
file(APPEND ${unfile} " \"\${CMAKE_COMMAND}\" ARGS \"-E remove \\\"\${file}\\\"\"\n")
file(APPEND ${unfile} " OUTPUT_VARIABLE rm_out\n")
file(APPEND ${unfile} " RETURN_VALUE rm_retval\n")
file(APPEND ${unfile} " )\n")
file(APPEND ${unfile} " IF(\"\${rm_retval}\" STREQUAL 0\)\n")
file(APPEND ${unfile} " ELSE(\"\${rm_retval}\" STREQUAL 0\)\n")
file(APPEND ${unfile} " MESSAGE(FATAL_ERROR \"Problem when removing \\\"\${file}\\\"\")\n")
file(APPEND ${unfile} " ENDIF(\"\${rm_retval}\" STREQUAL 0)\n")
file(APPEND ${unfile} " ELSE(EXISTS \"\${file}\")\n")
file(APPEND ${unfile} " MESSAGE(STATUS \"File \\\"\${file}\\\" does not exist. \")\n")
file(APPEND ${unfile} " ENDIF(EXISTS \"\${file}\")\n")
file(APPEND ${unfile} "ENDFOREACH(file)\n")
生成支持包卸载所需要的程序。注意,必须在执行包安装(make install)所在的相同路径卸载。
CMakeLists.txt
cmake_minimum_required(VERSION 2.6.0)
project(TestInstall)
# Add to module path, so we can find our cmake modules
list( APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
include_directories( ${PROJECT_SOURCE_DIR}/include)
add_library(test_install STATIC src/test_install.cpp) #改成shared就成了动态库
#The following will do install
include(install_package)
install_package(#使用了前面文件中定义的函数install_package并设置相关变量
PKG_NAME TestInstall
LIB_NAME test_install
VERSION 0.2
DESCRIPTION "installation test"
INSTALL_INCLUDE_DIR true
DESTINATION ${CMAKE_INSTALL_PREFIX}
#INCLUDE_DIRS ${REQUIRED_INCLUDE_DIRS}
#LINK_LIBS ${REQUIRED_LIBRARIES}
)
#The following can make `sudo make uninstall` work
include(${CMAKE_MODULE_PATH}/cmake_uninstall.cmake.in)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
- 文件第一二行不用解释,第三行
list( APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
是用来告诉CMake在路径${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules
中找到我们刚才那一堆不明所以的文件。其中${CMAKE_CURRENT_SOURCE_DIR}
指代你当前CMakeLists的路径。
- 第四行
include_directories( ${PROJECT_SOURCE_DIR}/include)
是用来指定头文件的位置。我们在源文件中写了#include <test_install.h>
,编译时程序本来找不到这个文件在哪里,有了上面那一行,它就知道要在${PROJECT_SOURCE_DIR}/include
路径下去寻找该头文件。其中${PROJECT_SOURCE_DIR}
指代当前项目的路径,此处和${CMAKE_CURRENT_SOURCE_DIR}
指向一样的路径,但是大家应该看过有些大型库有多个CMakeLists放在不同的文件夹里,我们称他们为底层文件夹,后者可以指代底层文件夹的位置(所以叫CMAKE_CURRENT...
)。前者则永远指向顶层的位置。CMake里有许多自带的变量指向特殊的位置,可以在下面网址中查看,不再多表。
Useful Variables · Wiki · CMake / Community · GitLab
- 第五行
add_library(test_install STATIC src/test_install.cpp)
把我们之前写的源文件做成一个名叫test_install
的库文件。这里有一个名叫STATIC
的标志,表明我们建立的这个库文件是静态库。如果我们想建立动态库,则要用SHARED
代替STATIC
。
- 第六行命令
include(install_package)
让我们CMake找到并能使用我们之前在cmake_modules
文件夹中复制进去的install_package.cmake
文件。这样我们才能使用接下来叫install_package
的命令(函数)。
install_package(
PKG_NAME TestInstall
LIB_NAME test_install
VERSION 0.2
DESCRIPTION "installation test"
INSTALL_INCLUDE_DIR true
DESTINATION ${CMAKE_INSTALL_PREFIX}
#INCLUDE_DIRS ${REQUIRED_INCLUDE_DIRS}
#LINK_LIBS ${REQUIRED_LIBRARIES}
)
install命令第一行PKG_NAME TestInstall
指定我们要安装的库在调用时要使用名称,以后我们要在其他程序中使用该库就用find_package(TestInstall REQUIRED)
命令。install第二行LIB_NAME test_install
。是我们要输出的静态库的名称,有了这一行我们就会在运行完cmake安装程序之后得到一个叫test_install.a
的文件。install第三行指当前库的版本,我随便写了一个。第四行形容该库的内容。install第五行INSTALL_INCLUDE_DIR true
表示我们要把include
文件夹中的文件都安装到系统文件夹中。install第六行DESTINATION ${CMAKE_INSTALL_PREFIX}
表示要安装的库的位置是${CMAKE_INSTALL_PREFIX}
,它也是CMake自带的路径变量,指代/usr/local
。Install第六七行暂时注释,我们在第二个例子会用到。
- 第七八行命名
include(${CMAKE_MODULE_PATH}/cmake_uninstall.cmake.in)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
第七行表示包含并能使用我们在cmake_modules
放置的cmake_uninstall.cmake.in
文件,第八行大家不用管具体什么意思。可能大家在使用了sudo make install
安装了一系列文件之后会有想删除的经历,但是你会发现有些不成熟的库并没有一键删除的功能,你经常需要去系统文件夹里手动使用sudo rm
之类的删除,上面两行搭配install_package
使用,你在想删除库时只需要在build
文件夹里输入sudo make uninstall
就可以把install的文件都删除了。这里要注意的是你必须使用install_package
安装的文件uninstall
命令才能找到并删除,你要是使用自己或者其他方式把库文件之类安装到其他位置,这个命令是删除不了安装的文件的。
我们之前说了这么多次系统文件夹,这个系统文件夹到底是什么在什么位置?我们个人安装的库,一般头文件在/usr/local/inlcude
里,静态库或者动态库文件在/usr/local/lib
里,总之这儿系统文件夹就泛指/usr/local
下的文件夹了。那么我们现在具体来看看上面的命名会把程序安装到哪儿。
cd到build文件夹里去。使用
cmake ..
有类似于下面内容
它大概表示我们instal_package.cmake里有个命令可能在以后的版本中消除掉,所以有警告,现在是没问题的,等以后有问题大家再自己去改吧,我就不管了(Em....)。
接着再使用
make
sudo make install
就可以看到类似于下面的内容
执行安装后,对于找该库的至关重要的文件FindTestInstall.cmake
文件被安装到了/usr/local/share/TestInstall
文件夹里,有了这个文件,使用find_package
命令才能找到该库。其他几个cmake文件大家可以自行上网看看他们的作用。头文件被安装到了/usr/local/include下面,如果有域名,则会创建对应的目录。库文件和模块配置文件被安装到了/usr/local/lib/的相关目录下。
上述安装的结果,允许我们使用模块方式和配置方式两种模式找到TestInstall这个包。
我们尝试卸载一下这个包,记得也要在build目录下才可以哦,要不然没有uninstall命令哦。
在独立工程中使用find_package()查找已安装自定义模块
随便在某个位置建立一个文件夹check_install
,在其中建立一个叫check_install_1.cpp
文件,CMakeLists.txt文件和build文件夹,输入下面内容检测库是否安装。
#include "test_install.h" //前面生成模块中的头文件
int main(){
Print check_print;
check_print.PrintHelloWorld();
}
再在同样的位置建立文件CMakeLists.txt,输入
cmake_minimum_required(VERSION 2.6.0)
project(CheckInstall)
# Add to module path, so we can find our cmake modules
#list( APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
find_package(TestInstall REQUIRED)
#find_package(TestInstall2 REQUIRED)
add_executable(check_install_1 check_install_1.cpp)
target_link_libraries(check_install_1 ${TestInstall_LIBRARIES})
#add_executable(check_install_2 check_install_2.cpp)
#target_link_libraries(check_install_2 ${TestInstall2_LIBRARIES} ${TestInstall_LIBRARIES})
注释部分是我们后面要用的内容,其他内容就是普通的找库TestInstall
并把我们的程序链接到库上的几行代码,相信大家都比较熟悉。cd到build文件夹使用
cmake ..
make
./check_install_1
你就能看到屏幕上输出了hello world
了。证明我们库安装并正常使用。
在前面我们卸载了TestInstall这个包之后,我们编译按check_install_1,
发现找不到TestInstall这个包。
我们重新安装自定义模块TestInstall,然后编译check_install
这里发现我们可以找到TestInstall这个包并使用里面的函数啦。
自定义包链接另一个自定义包
更多时候,我们要安装的库本身就链接了其他库的。这时候CMakeLists中的内容稍有不同,现在我们来建立一个叫cmake_tutorial_2
的文件夹,里面建立几个C文件使用我们第一个例子创造的库并安装。在cmake_tutorial_2
里创建include, src, cmake_modules, build
文件夹以及CMakeLists.txt
。include
中创建test_install_2.h
,内容如下
#pragma once
#include <test_install.h>
class Print2{
public:
Print print;
void PrintHelloWorldAgain();
};
指示把第一个库的类Print的对象作为成员并加了一个叫void PrintHelloWorldAgain()
的函数。这个函数的内容定义在src
的test_install_2.cpp
中,只是简单的使用print
对象调用第一个库中的PrintHelloWorld
函数。test_install_2.cpp
中内容如下:
#include <test_install_2.h>
void Print2::PrintHelloWorldAgain(){
print.PrintHelloWorld();
}
CMakeLists.txt中的内容如下
cmake_minimum_required(VERSION 2.6.0)
project(TestInstall2)
# Add to module path, so we can find our cmake modules
list( APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
find_package(TestInstall REQUIRED)
include_directories( ${PROJECT_SOURCE_DIR}/include)
include_directories( ${TestInstall_INCLUDE_DIRS})
add_library(test_install_2 STATIC src/test_install_2.cpp)
set(REQUIRED_INCLUDE_DIRS ${TestInstall_INCLUDE_DIRS})
set(REQUIRED_LIBRARIES ${TestInstall_LIBRARIES})
#The following will do install
include(install_package)
set(ICPCUDA_LIBRARIES icpcuda)
install_package(
PKG_NAME TestInstall2
LIB_NAME test_install_2
VERSION 0.2
DESCRIPTION "installation test 2"
INSTALL_INCLUDE_DIR true
DESTINATION ${CMAKE_INSTALL_PREFIX}
INCLUDE_DIRS ${REQUIRED_INCLUDE_DIRS}
LINK_LIBS ${REQUIRED_LIBRARIES}
)
#The following can make `sudo make uninstall` work
include(${CMAKE_MODULE_PATH}/cmake_uninstall.cmake.in)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
大体上和第一个库的CMakeLists一样,以下不同
find_package(TestInstall REQUIRED)
找到第一个库。
include_directories( ${TestInstall_INCLUDE_DIRS})
找到第一个库的头文件。接着
set(REQUIRED_INCLUDE_DIRS ${TestInstall_INCLUDE_DIRS})
set(REQUIRED_LIBRARIES ${TestInstall_LIBRARIES})
把库二需要连接的库头文件路径另取名为REQUIRED_INCLUDE_DIRS
和REQUIRED_LIBRARIES
。这一步这儿不是必须的。但是如果我们的库二连接了很多库A,B,C,
我们可以用
set(REQUIRED_INCLUDE_DIRS ${A_DIRS} ${B_DIRS} ${C_DIRS})
set(REQUIRED_LIBRARIES ${A_LIBRARIES} ${B_LIBRARIES} ${C_LIBRARIES})
把库的头文件和库本身命名到同一个变量中。这样我们在install_package
中就可以用
INCLUDE_DIRS ${REQUIRED_INCLUDE_DIRS}
LINK_LIBS ${REQUIRED_LIBRARIES}
把我们的新库真正连接到它自己使用的第三方库上面去了。这时候我们进入build文件夹使用cmake .. make sudo make install
一系列命令安装这个库。
安装好之后回到我们之前的check_install
文件夹把它的CMakeLists.txt中的注释内容
#find_package(TestInstall2 REQUIRED)
...
#add_executable(check_install_2 check_install_2.cpp)
#target_link_libraries(check_install_2 ${TestInstall2_LIBRARIES} ${TestInstall_LIBRARIES})
取消注释,进入build文件夹再make一下,生成并运行check_install_2
可以看到hello_world
又出现在了屏幕上,证明我们安装并连接成功。注意这儿target_link_libraries不仅连接了TestInstall2_LIBRARIES
,仍然连接了TestInstall_LIBRARIES
的,不然编译会出现错误。
整个工程的最初目录结构如下:
最后,对于安装到非/usr/local/目录下的方式,好像我前面看过一个帖子,但是忘记了。
参考链接:https://www.jianshu.com/p/53cc5f52b37e