大家经常在网上使用一些别人写好的库,大概步骤是先用github下载下来,在库的源文件夹中使用如下命令安装
cd <libABC_path>
mkdir build
cd build
cmake ..
make
sudo make install
通过上面的一系列命令就把这个库安装到系统里了。如果我们在写其他C程序中调用了该库,那么我们就需要在编译该程序的CMakeLists中写如
find_package(libABC REQUIRED)
来找到这库并使用,上面的这个过程偶尔使用cmake编译程序的应该都会比较清楚。那么我们如果自己想建立一个这样的库并被其他程序找到,该怎么做呢?
首先我必须说我对其中的某些细节并不了解,只是使用了一个捷径。当我去查CMake官网的资料时头都大了,写了很多,但是一个例子也没有!根本看不下去。能找到的非官网的例子有些过于冗长,又不想看。如果你也是这样,对CMake不求甚解,那么你也许可以和我走类似的捷径。
下面我们写两个非常简单的程序并把它编译成库安装到系统文件夹里。所有的文件已经写在github里,文末附上链接。
首先建立一个文件夹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
中复制几个文件进去,他们都已经在github里,这几个文件分别叫做FindPackage.cmake, cmake_uninstall.cmake.in, install_package.cmake, PackageConfig.cmake.in, PackageConfigVersion.cmake.in, PkgConfig.pc.in
。这些文件都是些毛啊?其实我也没有去关心他们具体的内容是什么,但是可以直接使用来他们安装我们的库,库不同也不用做什么修改,他们自己就好像CMake的自己的库。我们实验室的不少代码都使用上面的文件来安装。
打开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)
#The following will do install
include(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
就可以看到类似于下面的内容
咱们一行行来感受一下就能知道具体我们的库被安装到哪儿了。
头文件
test_install.h
被安装在/usr/local/include/
里,库文件被安装到/usr/local/lib
里。对于找该库的至关重要的文件FindTestInstall.cmake
文件被安装到了/usr/local/share/TestInstall
文件夹里,有了这个文件,使用find_package
命令才能找到该库。其他几个cmake文件大家可以自行上网看看他们的作用。到这儿我们就不用管这么多了,试试看现在一个C程序里能不能找到该库。随便在某个位置建立一个文件夹
check_install
,在其中建立一个叫check_install_1
文件,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
了。证明我们库安装并正常使用。
安装使用了其他库的库文件
更多时候,我们要安装的库本身就链接了其他库的。这时候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();
};
指示把第一个库的类的对象作为成员并加了一个叫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
的,不然编译会出现错误。
注意
1:由于我对CMake并没有过深的研究,以上的内容可能有部分表述不清楚或者错误,见谅。
2:如果你在check_install
编译时出现例如...By not Providing FindTestInstall.cmake
文件这类错误,表明之前安装的库仍然没找到,我在生成其他库时偶尔遇到过这个问题但是没清楚具体原因。不过解决方法很简单,我们之前编译第一个库的时候在/usr/local/share/FindTestInstall
生成了FindTestInstall.cmake
。我们把这个文件复制到check_install
文件夹里新建的文件夹cmake_modules
中,并把它CMakeLists.txt中的
#list( APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules)
这行取消掉注释,再编译就没有问题了。
3:install_package.cmake中有一些其他功能,具体请看它里面的注释并且自己实验。我们这个tutorial就讲这么多。
4:上述文件只在ubuntu和macOS测试过,没有在windows或者其他系统下测试过
github地址
https://github.com/zhaozhongch/cmake_tutorial
这个repo中有三个文件夹
cmake_tutorial_1就是我们的库1,cmake_tutorial_2就是我们的库2。check_install是用来检测安装库是否成功的程序。他们的内容前面都已经讲到,大家只需要下载下来在他们里建立一个build文件夹自己build检测即可。