CMake安装库到系统文件夹并被find_package找到

大家经常在网上使用一些别人写好的库,大概步骤是先用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.txtsrc用来放程序源文件,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 ..

有类似于下面内容


cmake_result.png

它大概表示我们instal_package.cmake里有个命令可能在以后的版本中消除掉,所以有警告,现在是没问题的,等以后有问题大家再自己去改吧,我就不管了(Em....)。
接着再使用

make
sudo make install

就可以看到类似于下面的内容

install_result.png

咱们一行行来感受一下就能知道具体我们的库被安装到哪儿了。
头文件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()的函数。这个函数的内容定义在srctest_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_DIRSREQUIRED_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检测即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 本文不介绍cmake命令使用方法,也不讲CMakeLists.txt的语法,有需要的读者可以看我另外相关的文章即可...
    konishi5202阅读 1,096评论 0 5
  • 注:首发地址 1. 前言 当在做 Android NDK 开发时,如果不熟悉用 CMake 来构建,读不懂 CMa...
    cfanr阅读 24,214评论 1 53
  • 一、 概述 CMake构建系统通过ROS包中的CMakeList.txt来构建软件包。互相依赖的包都包含一个或者多...
    茶色少年阅读 6,452评论 0 8
  • CMake学习 本篇分享一下有关CMake的一些学习心得以及相关使用。 本文目录如下: [1、CMake介绍] [...
    AlphaGL阅读 12,218评论 11 79
  • 引用cmake学习笔记-cmakelist.txt创建项目示例cmake的介绍和使用 Cmake实践推荐cmake...
    scott_yu779阅读 5,790评论 0 3