Last update 2018/8/8html
先执行cmake生成makefile,而后看看里面的内容,(至少在ubuntu16.04上的cmake3.5.1上),有以下内容提供:python
# Help Target help: @echo "The following are some of the valid targets for this Makefile:" @echo "... all (the default if no target is provided)" @echo "... clean" @echo "... depend" @echo "... edit_cache" @echo "... rebuild_cache" @echo "... net_demo" @echo "... src/net_demo.o" @echo "... src/net_demo.i" @echo "... src/net_demo.s" .PHONY : help
其中net_demo
是我本身的build target,能够忽略。能够看到,提供了edit_cache
选项,则经过make edit_cache
能够交互式的修改一些变量,挺好的。ios
必须用cmake
不少开源项目如KDE、VTK、OpenCV、Caffe等,都使用cmake来构建。要玩转这些项目,就须要掌握cmake。并且趋势是cmake会更加流行。c++
实际状况每每是,系统预装的opencv不够用,尝试编译opencv的时候遇到cmake各类问题,被折腾的死去活来,因而决定好好学一下cmake。git
cmake资料不够好
cmake经历了多个版本发展,如今(2017.05.05)它的官方文档比原来有了不少进步。github
现有的shell
小白视角
从一个cmake小白的角度来总结cmake的用法。ubuntu
cmake提供命令行方式,以及图形界面方式(包括cmake-GUI和ccmake)。这里仅使用cmake命令行方式,由于更简单直接。windows
使用ubuntu16.04, cmake3.5版。其余系统和cmake版本问题也都不大,由于本文尽可能作到具有较好的通用性。浏览器
先确保安装了gcc,g++和cmake
最简单的cmake项目,不使用IDE(如CLion),也不考虑什么编码风格的,就是干,简单粗暴!
编辑两个文件:main.cc和CMakeLists.txt。它们放在你的项目目录,好比~/work/hello-cmake
main.cc
#include <iostream> using namespace std; int main(){ cout << "Hello World!" << endl; return 0; }
CMakeLists.txt
cmake_minimum_required (VERSION 2.8) project(hello-world) add_executable(hello main.cc)
开终端,运行这些命令:
cd ~/work/hello-cmake #进入你的cmake项目目录 cmake . #执行cmake make #执行make
能够用Visual Studio来编译
假设C++代码和CMakeLists.txt文件内容相同。那么打开cmd执行以下命令,或者把以下命令保存到一个文件(例如build.bat)中再执行这个文件:
# 在源码相同目录下构建 cmake -G "Visual Studio 14" #生成.sln文件 cmake --build . #调用native build tool。在windows上,至关因而打开.sln而后手动点击构建。
或者在专门的文件夹里面编译:
#固然,把编译出来的东西单独放到一个目录也是OK的 mkdir build cd build cmake -G "Visual Studio 14" .. cmake --build . cd ..
以及,能够指定用Release模式,x64架构:
BUILD_DIR=build rm -rf $BUILD_DIR mkdir -p $BUILD_DIR cd $BUILD_DIR cmake \ -G "Visual Studio 14 Win64" \ .. cmake --build . --config Release # 在windows上,必须在build的时候指定编译类型,而不是cmake的阶段
或者用Unix的构建工具套件,这里使用的是tdmgcc64。安装以后把mingw32-make.exe复制一份为make.exe再执行
mkdir buildUnix cd buildUnix cmake -G "Unix Makefiles" .. make cd ..
cmake命令会在提供的路径(上例为".")下,找到CMakeLists.txt并执行它。执行成功后生成makefile(或其余相似的,目前阶段就认为是makefile好了);执行make,会根据makefile内容去执行。
至于cmake和make执行了哪些操做,须要了解cmake的语法,以及makefile的编写规则。有了cmake,其实能够忽略makefile,不妨认为makefile就是写给编译器gcc看的。
一般cmake运行后产生若干文件,和源码目录混杂在一块儿并非一个好的选择。一般新建一个build目录,在build目录执行cmake。
我的倾向于建一个脚本,每次用cmake构建时,参数比较多,用这个脚本比较方便:
compile.sh
#!/bin/bash set -x #把本行后的脚本执行内容,打印到屏幕。用于调试 set -e #本行后,若是某行执行结果返回值不是true,那么终止 LOG="log.build" touch $LOG rm $LOG exec &> >(tee -a "$LOG") #将屏幕输出内容,同时写入log文件:便于后续查找 echo "Logging to $LOG" BUILD_ROOT=build if [ -d $BUILD_ROOT ]; then rm -rf $BUILD_ROOT fi mkdir -p $BUILD_ROOT cd $BUILD_ROOT echo "building root folder is $BUILD_ROOT" echo "Now do cmake" cmake .. echo "Now do make" make -j8 echo "Done"
执行构建脚本:
chmod +x compile.sh ./compile.sh cd build ./hello
自顶向下看,cmake执行的是给定路径下的CMakeLists.txt,好比"."表示当前路径,".."表示当前目录的父目录。
CMakeLists.txt中指定项目的输入和输出:
输出:产生的可执行文件(或者库文件?)
输入:产生输出所依赖的源文件(以及库文件?)
CMakeLists.txt中经过cmake的语法编写相应代码语句,这些语句被cmake解释执行。进而产生makefile,用make去执行就完成编译。
cmake语法并不很复杂,每每翻看下cmake的官方手册就能知道某个变量、命令、标准的各类细节了。
遇到cmake指令看不懂,直接看文档,基本上解决问题。
https://cmake.org/cmake/help/latest/
实在是忍受不了查看文档时刷一个个网页一直出不来结果的状况。本身动手丰衣足食。若是你网络比较好,能够跳过这一节。
查看哪些apt包是cmake的文档:
aptitude search cmake
结果显示cmake-doc
提供了文档,cmake-data
则是另外一种形式的文档。
安装cmake及其文档:
sudo apt install cmake cmake-doc cmake-data #安装cmake文档 dpkg -L cmake-doc #查看cmake-doc包安装位置
发现是在/usr/share/doc/cmake-data目录,它的马甲目录(连接)是/usr/share/doc/cmake-doc,那就开启来好了:
cd /usr/share/doc/cmake-data/html python -m SimpleHTTPServer 4002 #用python开一个本地http服务器
浏览器访问http://localhost:4002
查看文档。
“另外一种形式的文档”呢?在/usr/share/cmake-3.5/Help
目录,是.rst
格式文档,要用sphinx编译:
sudo pip install sphinx #先确保装了pip cd /usr/share/cmake-3.5/Help sudo sphinx-quickstart #按照提示来,惟一须要注意的是autogen选择y。这一步生成makefile cd _build sudo make html #编译生成html cd html python -m SimpleHTTPServer 4003
能够把上述开启文档的http服务器命令用tmux开启。
查询某个命令:http://10.10.10.53:4003/command/
查询某个变量:http://10.10.10.53:4003/variable/
查询某个手册:http://10.10.10.53:4003/manual/
查询某个模块:http://10.10.10.53:4003/module/
查询发行日志:http://10.10.10.53:4003/release/
查询 policy :http://10.10.10.53:4003/policy/ 包含了各类cmake规范
原本,直接查看手册中find_package()
一节便可。
鉴于现有一些博客对于find_package()
这条命令介绍不够好,好比这篇内容陈旧、不全面;好比这篇纯粹是官方文档翻译,而官方文档不够突出重点,因此再罗嗦一小节的内容。
平时用cmake,遇到问题最多就是这个find_package()
命令的使用致使的。最多见于寻找opencv
包的状况。
这里喷一下opencv:要想用opencv各类特性,就要自行把opencv_contrib
编译进去。编译时候还会出现各类3rdparty的包从github上没法下载要手动解决的问题,不方便。
find_package()
:
先从CMAKE_MODULE_PATH
变量表示的路径下去找Find<name>.cmake
文件;
若是失败,则在CMAKE安装目录/share/cmake-x.y/Modules
目录下查找Find<name>.cmake
文件
若是上一步失败,则查找<Name>Config.cmake
或者<lower-case-name>-config.cmake
文件。按8大顺序查找你想要的包,若是前一个里面找到了就不去后面的找,还能够经过变量关闭某个查找顺序项
好比如今要找opencv的包。那么它的查找顺序是(都是在寻找<Name>Config.cmake
或者<lower-case-name>-config.cmake
):
在CMAKE_PREFIX_PATH
变量所表示的路径下寻找。
换言之,CMAKE_PREFIX_PATH
有最高的查找优先级。
在find_package()
参数列表中填写NO_CMAKE_PATH
则跳过该查找项。
<package>_DIR CMAKE_PREFIX_PATH CMAKE_FRAMEWORK_PATH CMAKE_APPBUNDLE_PATH
对于OpenCV,就是OpenCV_DIR
了。
在find_package()
参数列表中填写NO_CMAKE_ENVIRONMENT_PATH
跳过该查找项。
find_package()
的HINTS参数指定
系统环境变量PATH里寻找。
在find_package()
参数列表中填写NO_SYSTEM_ENVIRONMENT_PATH
跳过该查找项。
搜索在CMake GUI中最新配置过的工程的构建树。在find_package()
参数列表中填写NO_CMAKE_BUILDS_PATH
跳过该查找项。
搜索存储在CMake用户包注册表(User Package Registry)中的路径。
在find_package()
参数列表中填写NO_CMAKE_PACKAGE_REGISTRY
或者设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY
变量值为TRUE
跳过该查找项。(彷佛Linux下没啥用!)
(update@2018-07-31 00:47:33 今天被这个User Package Registry坑惨了!具体实例和解决方法看SO上的一个问答:how-to-avoid-cmake-to-read-in-its-system-cache-home-cmake。简单说,Caffe的cmake脚本中有一句export Caffe,这会向User Package Registry写东西,在ubuntu下的表现就是在~/.cmake/package/Caffe路径下写入一个文件,文件名字是一个hash值,文件内容是cmake编译的caffe的路径。一旦有多个版本的caffe存在而且都用了cmake编译,而若是前面1~5的设置路径有错误,那么就加载这个~/.cmake/package/Caffe/xxx文件中的路径。很坑!)
CMAKE_SYSTEM_PREFIX_PATH CMAKE_SYSTEM_FRAMEWORK_PATH CMAKE_SYSTEM_APPBUNDLE_PATH
在find_package()
参数列表中填写NO_CMAKE_SYSTEM_PATH
选项跳过这些路径。
注意 这里测试发现,CMAKE_SYSTEM_PREFIX_PATH
是/usr/local;/usr;/;/usr;/usr/local
,若是前面的查找顺序项都失败或者被关闭了,那么在这一查找项上,会在/usr/local
这样的路径下,查找opencv
开头的目录,好比/usr/local/opencv-git-master
会被找到;而假如我把opencv的路径换成不以opencv开头,好比/usr/local/what-opencv
则不能找到opencv。
find_package()
参数列表中用PATHS
指定搜索路径。这些路径通常是硬编码的参考路径。。。。(太长并且我的以为没啥用,想看的去找手册好了)写了这么多查找顺序,其实就记住一个好了,先设定定CMAKE_PREFIX_PATH
变量,再用find_package()
去找包,保证万事大吉:
list(APPEND CMAKE_PREFIX_PATH "/opt/opencv-git-master") #引号里是个人opencv安装路径 find_package(OpenCV)
成功查找到包后,产生这些变量吗?
按照cmake手册的说法,find_package()
执行后会产生几个变量:
但尝试找opencv的包时,不管是apt装的opencv仍是自行编译的opencv,都是同样:并不是这几个变量都有值:
CMAKE_MODULE_PATH is:
-- Found OpenCV: /usr/local/opencv-git-master (found version "3.2.0")
OpenCV_FOUND is : 1
OpenCV_INCLUDE_DIRS is : /usr/local/opencv-git-master/include;/usr/local/opencv-git-master/include/opencv
OpenCV_INCLUDES is :
OpenCV_LIBRARIES is opencv_calib3d;opencv_core;opencv_features2d;opencv_flann;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_shape;opencv_stitching;opencv_superres;opencv_video;opencv_videoio;opencv_videostab;opencv_aruco;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_datasets;opencv_dnn;opencv_dnn_modern;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hdf;opencv_line_descriptor;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_reg;opencv_rgbd;opencv_saliency;opencv_stereo;opencv_structured_light;opencv_surface_matching;opencv_text;opencv_tracking;opencv_xfeatures2d;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto
OpenCV_LIBS is : opencv_calib3d;opencv_core;opencv_features2d;opencv_flann;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_shape;opencv_stitching;opencv_superres;opencv_video;opencv_videoio;opencv_videostab;opencv_aruco;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_datasets;opencv_dnn;opencv_dnn_modern;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hdf;opencv_line_descriptor;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_reg;opencv_rgbd;opencv_saliency;opencv_stereo;opencv_structured_light;opencv_surface_matching;opencv_text;opencv_tracking;opencv_xfeatures2d;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto
OpenCV_DEFINITIONS is :
```
好比读取jpeg图像,并转化到Lab空间以及灰度图像。使用到opencv库,利用find_package()
进行查找。代码以下:
main.cc
#include <iostream> #include <opencv2/opencv.hpp> using cv::Mat; using cv::imread; using cv::imshow; using cv::waitKey; using cv::cvtColor; using cv::COLOR_BGR2Lab; using cv::COLOR_BGR2GRAY; int main(){ // Load image Mat srcImage = imread("cat.jpg", -1); Mat dstImage; imshow("RGB Space", srcImage); // Convert to other color space cvtColor(srcImage, dstImage, COLOR_BGR2Lab); imshow("Lab Space", dstImage); cvtColor(srcImage, dstImage, COLOR_BGR2GRAY); imshow("Gray Scale", dstImage); waitKey(); return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.2) project(hello-cmake) list(APPEND CMAKE_PREFIX_PATH "/usr/share/OpenCV") # apt装的opencv #list(APPEND CMAKE_PREFIX_PATH "/usr/local/opencv-git-master") #自行编译的opencv message("CMAKE_MODULE_PATH is: ${CMAKE_MODULE_PATH}") find_package(OpenCV #NO_CMAKE_PATH #NO_CMAKE_ENVIRONMENT_PATH #NO_SYSTEM_ENVIRONMENT_PATH #NO_CMAKE_PACKAGE_REGISTRY #NO_CMAKE_SYSTEM_PATH ) message("OpenCV_FOUND is : ${OpenCV_FOUND}") message("OpenCV_INCLUDE_DIRS is : ${OpenCV_INCLUDE_DIRS}") message("OpenCV_INCLUDES is : ${OpenCV_INCLUDES}") message("OpenCV_LIBRARIES is ${OpenCV_LIBRARIES}") message("OpenCV_LIBS is : ${OpenCV_LIBS}") message("OpenCV_DEFINITIONS is : ${OpenCV_DEFINITIONS}") add_executable(hello main.cc) target_link_libraries(hello ${OpenCV_LIBS})
build.sh
#!/bin/bash set -x set -e LOG="log.build" touch $LOG rm $LOG exec &> >(tee -a "$LOG") echo "Logging to $LOG" BUILD_ROOT=build if [ -d $BUILD_ROOT ]; then rm -rf $BUILD_ROOT fi mkdir -p $BUILD_ROOT cd $BUILD_ROOT echo "building root folder is $BUILD_ROOT" echo "Now do cmake" cmake .. make -j8 echo "Done"
利用add_library()
生成库文件。
main.cc
#include <iostream> using namespace std; void smile(){ cout << "Nice smile" << endl; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.2) message("BUILD_SHARED_LIBS is ${BUILD_SHARED_LIBS}") #set(BUILD_SHARED_LIBS ON) # 设定BUILD_SHARED_LIBS来表示add_library构建共享库(shared)仍是静态库(static) message("BUILD_SHARED_LIBS is ${BUILD_SHARED_LIBS}") add_library(smile STATIC main.cc) #不过,add_library()显示地设定SHARED和STATIC,更直观
build.sh
其实每次个人build.sh
都是同样的:)
#!/bin/bash set -x set -e LOG="log.build" touch $LOG rm $LOG exec &> >(tee -a "$LOG") echo "Logging to $LOG" BUILD_ROOT=build if [ -d $BUILD_ROOT ]; then rm -rf $BUILD_ROOT fi mkdir -p $BUILD_ROOT cd $BUILD_ROOT echo "building root folder is $BUILD_ROOT" echo "Now do cmake" cmake .. make -j8 echo "Done"
如今,本身的库写好了,怎么用呢?一种方法是make install,另外一种方法是提供配置文件,好比mylibrary-config.cmake这种,后者须要配合.cmake.in
文件进行生成,能够参考官方wiki:https://cmake.org/Wiki/CMake/Tutorials/How_to_create_a_ProjectConfig.cmake_file
===================
2018年8月16日 20点46分
这里插播一个实际遇到的状况,挺有意思,领教了CMAKE_CXX_COMPILER
变量的厉害(采坑)。
小伙伴的ubuntu上要编译Caffe,官方源码下载后用CMake构建,到95%左右,convert_mnist_siamse_data.o这里会报错,说DSO Missing,具体指向的是libcstdc++.
开始时百思不得其解,由于查看g++和cmake,以及protobuf什么的,都是apt装的或者系统自带的,为啥缺库呢。
后来有人指出是连接阶段没有连接libstdc++.so.6。可是为啥要明确连接它?
个人Debug方法:利用message( )
函数(至关于C/C++里的printf( ))暴力输出各类连接库的名字,仍是不能发现问题,逐步溯源到CMake的输出,发现CMAKE_CXX_COMPILER
这个变量的取值出了问题。
CMAKE_CXX_COMPILER
CXX这个环境变量被设定了,设定的值为/usr/bin/gcc-5
CMake会读取这个$CXX 而后设定CMAKE_CXX_COMPILER为这个值,也就是用gcc-5替代了g++
因此后续编译的话都缺乏stdc++这个库
在终端里,unset CXX,或者把你的bashrc/zshrc里面的export CXX=/usr/bin/gcc-5
注释掉并退出从新进入shell,就能够正常使用CMAKE_CXX_COMPILER
这一变量来编译Caffe了。
这篇写的还能够:https://chaopei.github.io/blog/2018/10/cmake-variable.html
这篇更详细一些:https://riptutorial.com/zh-CN/cmake/example/11821/变量和全局变量缓存
若是但愿cmake -Dvar=value的方式,临时设定变量的值,则必须用set(VAR "some value" CACHE STRING "ignore this" [FORCE])的方式