cmake学习

cmake小练习:https://github.com/ME-TWM/cmake_practice

make工具通过调用makefile文件中的命令便可以对大型程序进行编译,而makefile文件中就包含了调用gcc去编译多个源文件的命令。

但是,有一个问题,如果我们的程序是跨平台的,如果换个平台makefile又要重新修改,这会很麻烦,所以就出现了cmake这个工具,通过cmake我们就可以快速创建出不同平台的makefile文件。

所以,cmake根据CMakeLists.txt来生成makefile文件。为了编译一个大型程序,你首先编写CMakeLists.txt。然后,通过cmake命令就可以生成makefile文件。然后通过make命令就可以使用这个makefile文件从而生成可执行文件。

一个CMakelists.txt就创建一个project(使用project(name)命令),一个project可以包含多个subproject,一般在顶层project(toplevel CMakeLists.txt,在最外围的目录)里生成一个可执行文件(add_executable),一个project里可以创建多个target(add_library或add_executable),一个target可能是一个静态库/动态库/二进制文件,在project里会把有依赖关系的target链接起来(target_link_libraries)

project(NAME)
这个命令会创建一个名称为NAMEproject,同时也会创建一个全局变量${PROJECT_NAME},其值为NAME,
下面截取自02-subproject
When a project is created using the `project()` command, CMake will automatically
create a number of variables which can be used to reference details about the project.
These variables can then be used by other sub-projects or the main project. For exampe,
to reference the source directory for a different project you can use.

[source,cmake]
----
    ${sublibrary1_SOURCE_DIR}
    ${sublibrary2_SOURCE_DIR}
----

The variables created by CMake are:

[cols=",",options="header",]
|=======================================================================
|Variable |Info
|PROJECT_NAME | The name of the project set by the current `project()`.

|CMAKE_PROJECT_NAME |the name of the first project set by the `project()`
command, i.e. the top level project.

|PROJECT_SOURCE_DIR |The source director of the current project.(当前这个cmakelists所在的路径)

|PROJECT_BINARY_DIR |The build directory for the current project.

|name_SOURCE_DIR | The source directory of the project called "name".
In this example the source directories created would be `sublibrary1_SOURCE_DIR`,
`sublibrary2_SOURCE_DIR`, and `subbinary_SOURCE_DIR`

|name_BINARY_DIR | The binary directory of the project called "name".
In this example the binary directories created would be `sublibrary1_BINARY_DIR`,
`sublibrary2_BINARY_DIR`, and `subbinary_BINARY_DIR`

|=======================================================================


CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
in source 编译:直接在工程顶层目录运行cmake命令
out-of-source 编译:在其他任何目录下运行cmake命令。

比如:out-of-source build一般在源文件的顶层目录中 新建build目录

cd build

然后  cmake .. -G"MinGW Makefiles"即可

这样所有的临时文件 都会放在build目录下不会和source有任何的瓜噶。


in source 编译:
指定编译所要求的最低cmake版本:
cmake_minimum_required(VERSION 2.6)
project (hello_cmake)
add_executable(${PROJECT_NAME} main.cpp)


add_executable(hello_cmake main.cpp)
会生成一个名为hello_cmake 的可执行文件,该文件通过main.cpp等源文件(可以是多个文件)编译而来

add_library()
function is used to create a library from some source files.
Eg:
add_library(hello_library STATIC
    src/Hello.cpp
)
This will be used to create a static library with the name libhello_library.a with
the sources in the +add_library+ call.(使用static时即生成.a格式的静态库)
除了static外,其实一共有这几种:
SHARED,动态库(.so)
STATIC,静态库(.a)
MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
如果我们想改生成的库的名字,可以用这个(被改为hello):
SET_TARGET_PROPERTIES(hello_library PROPERTIES OUTPUT_NAME "hello")

Eg2:
add_library(hello::library ALIAS hello_library)
just like "typedef".
hello::library is the same as hello_library now. They are 2 names of the same thing.
And please note that "hello::library" is just a name !!! NO namespace in CMake!!!!
SHARED:  generate the shared library from the library sources
ALIAS:   just like "typedef".

add_library() 用于创建库(生成的target是library文件),他的入参一般是纯粹定义了一堆函数(而没有类似main的执行入口)的源文件(对add_library()和target_include_libraries()而言,cmakelists中先调用add_library()来生成库A,再对库A调用target_include_libraries()来让库A生成的时候包含指定的路径(否则没生成库A的话也没办法把库A的名称当做target_include_libraries()的入参),同理也是先用add_executable()生成可执行文件,再调用target_include_libraries()来给欲生成的可执行文件链接其他库)

add_executable()用于创建可执行文件(生成的target是Binary文件)
(入参是带有main函数的源文件,一般手法是在其他cmakelists中调用add_library()来生成库,然后在调用调用add_executable()的cmakelists里面调用target_link_libraries来使用这些库)



target_include_directories()
When you have different include folders, you can make your compiler aware of them using function

eg:
target_include_directories(target  PRIVATE  路径A)
(对target(无论是用add_executable还是add_library生成)使用这个函数时,他自己的源代码(当前即当前这个project中add_library或add_executable指定的源文件)就能看见target_include_directories指定的路径A,相当于在这些源文件所在的目录中增加了路径A的内容!如果其他project使用target_link_libraries链接了这个target,则也会像在自己的cmakelists中加了这段代码一样(如果设置为 PUBLIC 的话),根据PRIVATE/INTERFACE/PUBLIC的设定,在其他project所对应的代码也能看见或看不见路径A,相当于 每个链接了当前这个target的project的源代码所在的目录都拥有了一份路径A的内容的拷贝!这样在这些project的源代码(即add_library中指定的源文件)就能像这个目录的内容直接拷贝到了这里一样来编码!)
The +PRIVATE+ identifier specifies the scope of the include.
The meaning of scopes are:
 +PRIVATE+ - the directory is added to this target's include directories
(比如这个target tar是由a.cpp生成,a.cpp里有这段代码:#include “include/lib.h”,使用target_include_directories(tar  PRIVATE  A/B)后,类似上面的#include代码编译器会解释为#include “A/B/include/lib.h”(lib.h这个头文件实际路径是A/B/include/lib.h))正如下面所言:
The directory passed to +target_include_directories+ will be the root of your
include directory tree and your C++ files should include the path from there to your header.
For this example you can see that we do it as follows in the cpp files:
#include "static/Hello.h"

* +INTERFACE+ - the directory is added to the include directories for any targets that link this library.
* +PUBLIC+ - As above, it is included in this library and also any targets that link this library.
(也就是相当于使用target_link_libraries()链接这个target的其他target也加上了这个函数代码(如即生成这个其他target的源文件的include代码路径前也加上了这个路径))

target_link_libraries()
When creating an executable that will use your library you must tell the compiler
about the library. This can be done using this function
eg:
target_link_libraries( hello_binary
    PRIVATE  
        hello_library
)
This tells CMake to link the hello_library against the hello_binary executable
during link time(注意第一个hello_binary是一个add_executable生成的可执行文件,第二个hello_library是一个add_library生成的lib库). It will also propagate any include directories with +PUBLIC+ or +INTERFACE+ scope
 from the linked library target.
An example of this being called by the compiler is
```
/usr/bin/c++ CMakeFiles/hello_binary.dir/src/main.cpp.o -o hello_binary -rdynamic libhello_library.a
```


为了打印出make命令执行时的完整信息,可以在后面加上参数verbose=1:
$ make VERBOSE=1


Create a sources variable with a link to all cpp files to compile
eg:
set(SOURCES
    src/Hello.cpp
    src/main.cpp
)




CMake offers the ability to add a `make install` target to allow a user to
install binaries, libraries and other files. The base install location is controlled
by the variable +CMAKE_INSTALL_PREFIX+ which can be set using ccmake or by calling
cmake with:
$cmake .. -DCMAKE_INSTALL_PREFIX=/install/location

CMAKE_INSTALL_PREFIX是这么一个变量:这个变量初始为空,由用户设定,里面保存一些路径,以分号分隔,在调用以下这些命令时,会在这些路径中搜索相应的入参:
find_package(), find_program(), find_library(), find_file(), find_path()
CMAKE_MODULE_PATH:概念和CMAKE_INSTALL_PREFIX一样,在调用以下命令时会在这个变量的路径中搜索:
include(),find_package()
在下面的install命令中,也会用到CMAKE_INSTALL_PREFIX




The files that are installed are controlled by the function:
install()
install用于指定在安装时运行的规则。它可以用来安装很多内容,可以包括目标二进制、动态库、静态库以及文件、目录、脚本等.执行完cmake ..      make    之后,还要执行 make install才能真正安装东西到指定路径(注意要以管理员身份执行,因为默认的写入路径需要写入权限)
eg:
install (TARGETS cmake_examples_inst_bin
    DESTINATION bin)
Install the binary generated from the target cmake_examples_inst_bin target to
the destination +${CMAKE_INSTALL_PREFIX}/bin+
(注意是TARGETS,因此后面可以带多个target做入参)
eg2:
install (TARGETS cmake_examples_inst
    LIBRARY DESTINATION lib)
Install the shared library generated from the target cmake_examples_inst target to
the destination +${CMAKE_INSTALL_PREFIX}/lib+
eg3:
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/
    DESTINATION include)
----
Install the header files for developing against the +cmake_examples_inst+ library
into the +${CMAKE_INSTALL_PREFIX}/include+ directory.
Eg4:
install (FILES cmake-examples.conf
    DESTINATION etc)
----
Install a configuration file to the destination +${CMAKE_INSTALL_PREFIX}/etc+

After `make install` has been run, CMake generates an install_manifest.txt file
which includes details on all installed files.
当CMakeLists.txt中有install命令时,需要在命令行执行完cmake,make命令后,还要(用超级权限)再执行 make install命令,CMakeLists.txt中的install命令才会真正执行

As mentioned the default install location is set from the +CMAKE_INSTALL_PERFIX+,
which defaults to `/usr/local/`
eg:
if( CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT )
  message(STATUS "Setting default CMAKE_INSTALL_PREFIX path to ${CMAKE_BINARY_DIR}/install")
  set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE STRING "The path to use for make install" FORCE)
endif()
----
This example sets the default install location to under your build directory.


If you wish to stage your install to confirm that all files are included the
`make install` target supports the DESTDIR argument.
```
make install DESTDIR=/tmp/stage
```
This will create the install path `${DESTDIR}/${CMAKE_INSTALL_PREFIX}` for all
your installation files.
In this example, it would install all files under the
path `/tmp/stage/usr/local`
即:
$cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local  
(注:/usr/local是CMAKE_INSTALL_PREFIX的默认值,后面这个参数不加也罢)
$make install DESTDIR=/tmp/stage





The build type can be set using the following methods.

  - Using a gui tool such as ccmake / cmake-gui

image::cmake-gui-build-type.png[cmake-gui build type]

  - Passing into cmake

[source,cmake]
----
cmake .. -DCMAKE_BUILD_TYPE=Release
----

## Set Default Build Type

The default build type provided by CMake is to include no compiler flags for
optimization. For some projects you may want to
set a default build type so that you do not have to remember to set it.

To do this you can add the following to your top level CMakeLists.txt

[source,cmake]
----
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message("Setting build type to 'RelWithDebInfo' as none was specified.")
  set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
  # Set the possible values of build type for cmake-gui
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
    "MinSizeRel" "RelWithDebInfo")
endif()

设置 compile flags 的几种方式:
第一种:
target_compile_definitions(cmake_examples_compile_flags
    PRIVATE EX3
)
This will cause the compiler to add the definition +-DEX3+ when compiling the target.

第二种:
using the +CMAKE_C_FLAGS+ or +CMAKE_CXX_FLAGS+ variables.
Eg:
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)
The values `CACHE STRING "Set C++ Compiler Flags" FORCE` from the above command
are used to force this variable to be set in the CMakeCache.txt file.
Once set the +CMAKE_C_FLAGS+ and +CMAKE_CXX_FLAGS+ will set a compiler flag / definition globally for all targets in this directory or any included sub-directories. This method is not recommended for general usage now and the +target_compile_definitions+ function is preferred.
第三种:
cmake .. -DCMAKE_CXX_FLAGS="-DEX3"

第四种:
add_compile_options
(参考:https://blog.csdn.net/10km/article/details/51731959)

注意!在cmakelists中,设置选项时-D后面的才是参数!前面的D是用来说明后面的是参数!
例如:
cmake .. -DCMAKE_CXX_FLAGS="-DEX3"
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)
target_compile_definitions(cmake_examples_compile_flags
    PRIVATE EX3
)
同理,为了说明这是一个库,会在库名前面加上lib
上面的编译选项EX2,EX3只是一个标识符,没有什么特殊含义,在源文件中可能会这样使用,例如:
int main(int argc, char *argv[])
{
   std::cout << "Hello Compile Flags!" << std::endl;

   // only print if compile flag set
#ifdef EX2
  std::cout << "Hello Compile Flag EX2!" << std::endl;
#endif

#ifdef EX3
  std::cout << "Hello Compile Flag EX3!" << std::endl;
#endif

   return 0;
}


find_package()
This will search for CMake modules in the format
"FindXXX.cmake" from the list of folders in `CMAKE_MODULE_PATH`. On linux the
default search path will include `/usr/share/cmake/Modules`.
Most included packages will set a variable `XXX_FOUND`, which can be used to check
if the package is available on the system.
Eg:
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
it will search for CMake modules Boost  in the formant FindBoost .cmake from the list of folders in `CMAKE_MODULE_PATH`.
Boost_FOUND was used in the Cmakelists like this:
---------------
if(Boost_FOUND)
    message ("boost found")
    include_directories(${Boost_INCLUDE_DIRS})
else()
    message (FATAL_ERROR "Cannot find Boost")
endif()
-----------------
The arguments are:
  * Boost - Name of the library. This is part of used to find the module file FindBoost.cmake
  * 1.46.1 - The minimum version of boost to find
  * REQUIRED - Tells the module that this is required and to fail it it cannot be found
* COMPONENTS - The list of libraries to find.


When building with CMake it is possible to set the C and C++ compiler.
CMake exposes options to control the programs used to compile and link your code. These
programs include:

  * CMAKE_C_COMPILER - The program used to compile c code.
  * CMAKE_CXX_COMPILER - The program used to compile c++ code.
* CMAKE_LINKER - The program used to link your binary.
Eg:
cmake .. -DCMAKE_C_COMPILER=clang-3.6 -DCMAKE_CXX_COMPILER=clang++-3.6
After setting these options, when your run `make` clang will be used to compile your binary.


To call a CMake generator you can use the `-G` command line switch, for example:
----
cmake .. -G Ninja

After doing the above CMake will generate the required Ninja build files, which can be run
from using the `ninja` command.
(Ninja 是一个构建系统,与 Make 类似。作为输入,你需要描述将源文件处理为目标文件这一过程所需的命令。 Ninja 使用这些命令保持目标处于最新状态。与其它一些构建系统不同,Ninja 的主要设计目标是速度。 )


3 CHECK_CXX_COMPILER_FLAG
检查CXX编译器是否支持给定的flag
必须先include(CheckCXXCompilerFlag)
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG(<flag> <var>)
eg:
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
if(COMPILER_SUPPORTS_CXX11)#
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
    message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
就是在检查当前编译器是否支持c++11
CHECK_CXX_COMPILER_FLAG 赋值给的var是个bool型
`include(CheckCXXCompilerFlag)` tells CMake to include this function to make it available for use.

指定编译时的C++版本:
# set the C++ standard to C++ 11
set(CMAKE_CXX_STANDARD 11)
The `CMAKE_CXX_STANDARD` variable falls back to the closest appropriate standard without a failure. For example, if you request `-std=gnu++11` you may end up with `-std=gnu++0x`.
This can result in an unexpected failure at compile time.



指定在CMake目标中使用的C++功能
Calling the function target_compile_features on a target will look at the passed in feature and determine the correct compiler flag to use for your target.
target_compile_features look at the passed in feature and determine the correct compiler flag to use for your target.
The list of available features can be found from the CMAKE_CXX_COMPILE_FEATURES
eg:
# set the C++ standard to the appropriate standard for using auto
target_compile_features(hello_cpp11 PUBLIC cxx_auto_type)
message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")

  • add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])

    添加一个子目录并构建该子目录。

  • 命令解析

    • source_dir
      必选参数。该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。
    • binary_dir
      可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir
    • EXCLUDE_FROM_ALL
      可选参数。当指定了该参数,则子目录下的目标不会被父目录下的目标文件包含进去,父目录的CMakeLists.txt不会构建子目录的目标文件,必须在子目录下显式去构建。例外情况:当父目录的目标依赖于子目录的目标,则子目录的目标仍然会被构建出来以满足依赖关系(例如使用了target_link_libraries)
原文地址:https://www.cnblogs.com/tan-wm/p/14669096.html