Skip to content

Latest commit

 

History

History
564 lines (478 loc) · 18.3 KB

CMake_note.md

File metadata and controls

564 lines (478 loc) · 18.3 KB

CMake入门学习笔记

CMake is great. don't waste time on other C++ build tools, seriously.

1. CMake是什么?

CMake是一款工程构建工具,类似的工具还有autotoolqmakeScons等等。

具体见: 为什么选用cmake

随着功能的不断增加和复杂,我们写的C++程序也不可能再像Helloworld.cpp那样只有一个源文件了,整个程序工程中,会有很多的源文件与库文件,如何将他们组合,编译成可执行的程序,就是CMake作为工程构建工具的作用:它需要告诉计算机,整个复杂工程的文件之间有怎么样的关系。

这个过程通过一个叫CMakeList.txt的文件来进行。

  • CMakeLists.txt是使用CMake时唯一需要编写的文件:
    cmake_minimum_required(VERSION 2.6)
    project(itest)
    
    # C++标准
    set(CMAKE_CXX_STANDARD 11)
    
    # 指定参与编译的源文件
    add_executable(itest src/main.cpp src/cal/Calculator.cpp src/cal/Calculator.h)
    
    # 指定安装路径,make install 时运用
    install (TARGETS itest DESTINATION bin)
    install(DIRECTORY src/ DESTINATION include/itest FILES_MATCHING PATTERN "*.h")
    
    # 设置不同build类别时的编译参数
    #set(CMAKE_BUILD_TYPE "Debug")
    set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
    set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
一个示例

2. CMake怎么用?

根据CMake 入门实战 - Hahack的实例来学习记录。

原文C语言Demo源码出自《CMake入门实战》源码 - github, wzpan,本文改写为C++,代码地址 - github

  • Linux平台使用CMake生成Makefile并编译的流程:
  1. 编写CMake配置文件CMakeList.txt
  2. 执行命令cmake ${CMAKELIST_PATH}生成Makefile${CMAKELIST_PATH}CMakeList.txt所在的目录。
  3. 执行命令make进行编译。

2.1. 单个源文件的例子

2.1.1. 编写CMakeList.txt

以下面的Demo_1 - github的实例来学习程序为例子,项目中的一个源文件main.cpp如下:

/*
 * power - Calculate the power of number.
 * @param base: Base value.
 * @param exponent: Exponent value.
 *
 * @return base raised to the power exponent.
 */
 
#include <iostream>

using namespace std;

double power(double base, int exponent)
{
    int result = base;
    int i;

    if (exponent == 0) {
        return 1;
    }

    for(i = 1; i < exponent; ++i){
        result = result * base;
    }
    return result;
}
int main(int argc, char *argv[])
{
    if (argc < 3){
        // printf("Usage: %s base exponent \n", argv[0]);
        cout << "Usage: " << argv[0] << "base exponent" <<endl;
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);
    double result = power(base, exponent);
    // printf("%g ^ %d is %g\n", base, exponent, result);
    cout << base << "^" << exponent << " is " << result <<endl;
    return 0;
}
~/Demo_1/main.cpp

首先编写CMakeList.txt,并保存在与main.cpp文件同一个目录下:

~/Demo_1
    |
    +--main.cpp
    +--CMakeList.txt

一个最基本的CMakeList.txt是这样的,在这个例子中,我们只需要告诉计算机,我们要使用main.cpp文件,来编译一个名为Demo的可执行文件:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo_1)

# 指定生成目标
add_executable(Demo main.cpp)
~/Demo_1/CMakeList.txt

CMake中,#后面的内容为注释,命令不区分大小写,参数之间用空格分隔,基本格式为:

commandName(arg1 arg2 ...)

上面的CMakeList.txt中的三个命令:

  • cmake_minmum_required()用来指定所需的CMake的最低版本。
  • project()中的参数表示项目名称。
  • add_excutable()中有两个参数Demomain.cc,意思是将main.cc源文件编译成一个名为Demo的可执行文件。
2.1.2. 编译和运行 Demo_1 - github

在编写完CMakeList.txt之后,执行cmake ${CMAKELIST_PATH}

shi@shi-Z370M-S01:~/Demo_1$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_1

会在按照文件中的配置和参数,在当前目录生成编译所需要的一系列文件,其中包含Makefile文件。

  • cmake命令是可以附带各种参数的,在命令后用空格隔开使用,--build--target等。
~/Demo_1
    |
    +--CMakeFiles
    +--CMakeCache.txt
    +--cmake_install.cmake
    +--CMakeList.txt
    +--main.cpp
    +--Makefile

生成Makefile后再执行make ${MAKEFILE_PATH}命令即可编译得到Demo可执行文件。

shi@shi-Z370M-S01:~/Demo_1$ make
Scanning dependencies of target Demo
[ 50%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo

运行编译后的可执行文件:

shi@shi-Z370M-S01:~/Demo_1$ ./Demo 2 10
2^10 is 1024

2.2. 多个源文件的例子

2.2.1. 同目录下多个源文件 Demo_2 - github

在实际编程过程中,如果有多个源文件,比如将Demo_1.cpp中的power函数单独写进一个mathfunctions.cpp源文件中:

/*
 * power - Calculate the power of number.
 * @param base: Base value.
 * @param exponent: Exponent value.
 *
 * @return base raised to the power exponent.
 */

double power(double base, int exponent)
{
    int result = base;
    int i;

    if (exponent == 0) {
        return 1;
    }

    for(i = 1; i < exponent; ++i){
        result = result * base;
    }

    return result;
}
~/Demo_2/mathfunctions.cpp

然后在main函数中引用MathFunctions.cpppower函数来进行计算:

#ifndef POWER_H
#define POWER_H

extern double power(double base, int exponent);

#endif
~/Demo_2/mathfunctions.h
#include <iostream>

//  import power function
#include "MathFunctions.h" 

int main(int argc, char *argv[])
{
    if (argc < 3){
        // printf("Usage: %s base exponent \n", argv[0]);
        cout << "Usage: " << argv[0] << "base exponent" <<endl;
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);
    double result = power(base, exponent);
    //printf("%g ^ %d is %g\n", base, exponent, result);
    cout << base << "^" << exponent << " is " << result <<endl;
    return 0;
}
~/Demo_2/main.cpp

令工程变成如下结构:

~/Demo_2
    |
    +--CMakeList.txt
    +--main.cpp
    +--MathFunctions.cpp
    +--MathFunctions.h

这时我们只需要在CMakeList.txt中的add_executable()命令上添加新参数,变成这样就可以了:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo_1)

# 指定生成目标
add_executable(Demo "main.cpp" "MathFunctions.cpp")

这里我们通过CMake告诉了计算机,我们要使用主文件main.cpp和函数文件MathFunctions.cpp来生成可执行程序Demo

但是在源文件数量较多的时候,在add_executable()命令上添加新参数就会变得很麻烦,这时候我们可以使用aux_source_directory()命令:

aux_source_directory(<dir> <variable>)

这个命令会查找<dir>参数的目录下所有的源文件,然后将结果存进<variable>变量中,因此可以修改CMakeList.txt如下:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo_1)

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 指定生成目标
add_executable(Demo ${DIR_SRCS})

编译运行一下:

shi@shi-Z370M-S01:~/Demo_2$ cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_2
shi@shi-Z370M-S01:~/Demo_2$ make
Scanning dependencies of target Demo
[ 33%] Building CXX object CMakeFiles/Demo.dir/MathFunctions.cpp.o
[ 66%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo
shi@shi-Z370M-S01:~/Demo_2$ ./Demo 2 10
2^10 is 1024
2.2.2 多个目录和多个源文件 Demo_3 - github

接下来我们将MathFunctions.cppMathFunctions.h文件移动到math目录下:

~/Demo_3
    |
    +--CMakeList.txt
    +--main.cpp
    +--math/
        |
        +--CMakeList.txt
        +--MathFunctions.cpp
        +--MathFunctions.h

这时候项目工程分成了主程序和库文件两个部分,这时候需要我们分别在根目录~/Demo_3库目录~/Demo_3/math/各编写一个CMakeList.txt文件,将库目录~/Demo_3/math/里的文件编译成静态库再由根目录主程序main.cpp中的main()函数调用。

库目录~/Demo_3/math/下的CMakeList.txt

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS变量
aux_source_directory(. DIR_LIB_SRCS)

add_library(MathFunctions ${DIR_LIB_SRCS})
~/Demo_3/math/CMakeList.txt

之前我们使用add_executable()命令,编译生成可执行文件,在这个库目录的CMakeList.txt文件中,我们使用了add_library()函数,来将库目录中的文件编译为静态连接库

这样我们先告诉了计算机,我们需要用库文件math目录下的源文件,组成一个名为MathFunctions的库(library)。

之后我们回到根目录~/Demo_3/中,修改根目录的CMakeList.txt文件如下:

# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)

# 项目信息
project(Demo_3)

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
# aux_source_directory(. DIR_SRCS)

# 添加 math 子目录
add_subdirectory(math)

# 指定生成目标
add_executable(Demo main.cpp)

# 添加链接库
target_link_libraries(Demo MathFunctions)
~/Demo_3/CMakeList.txt

在根目录新的CMakeList.txt中,我们使用add_subdirectory()命令,将库目录/math添加为一个辅助目录(subdirectory),告诉计算机我们将要用到这个目录下的文件或者库。

之后我们使用target_link_libraries()命令,将库目录/math下,已经在上一个CMakeList.txt中定义好的库MathFunctions与我们想要生成的可执行文件Demo相连接。

  • 不要忘记修改主目录下main.cpp的头文件引用:
// #include "MathFunctions.h"  old

#include "math/MathFunctions.h"  // new

编译并运行一下:

shi@shi-Z370M-S01:~/Demo_3$ cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_3
shi@shi-Z370M-S01:~/Demo_3$ make 
[ 50%] Built target MathFunctions
Scanning dependencies of target Demo
[ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo
shi@shi-Z370M-S01:~/Demo_3$ ./Demo 2 10
2^10 is 1024
2.2.3. 自定义编译选项

CMake可以自定义编译选项,从而可以允许我们根据用户的环境和需求,选择最合适的编译方案,比如可以将上一节的我们自己写的MathFunctions库设置为一个可选的库,从而可以自由选择编译时是使用我们自己创建的库,还是调用标准库中的数学函数。

为此我们需要在顶层CMakeLists.txt中添加选项。

首先需要在其中添加:

# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_BINARY_DIR}/config.h"
  )

configure_file()命令用来加入一个配置头文件config.h,这个文件将由CMakeconfig.h.in生成,通过这个机制,我们可以预定义一些参数和变量来控制代码生成。 然后再添加:

# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
       "Use provided math implementation" ON)
# 是否加入 MathFunctions 库
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/math")
  add_subdirectory (math)  
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

option()命令中,我们定义了一个名为USE_MYMATH变量,并设置其默认值为ON。 随后的if()根据USE_MYMATH变量的值来决定我们是否使用自己编写的MathFunctions库。如果是,则添加头文件目录${PROJECT_SOURCE_DIR}/math,子目录math,并将可选库地址存于EXTRA_LIBS

完整的CMakeList.txt如下:

# CMake最低版本号要求
cmake_minimum_required(VERSION 2.8)

# 项目信息
project(Demo_4)

# 加入一个配置头文件,用于处理CMake对源码的设置
configure_file(
  "${PROJECT_SOURCE_DIR}/config.h.in"
  "${PROJECT_BINARY_DIR}/config.h"
  )

# 是否使用自己的MathFunctions库
option(USE_MYMATH
       "Use provided math implementation"
	ON
  )

# 是否加入MathFunctions库
if (USE_MYMATH)
  # 添加头文件路径
  include_directories("${PROJECT_SOURCE_DIR}/math")
  # 添加math子目录
  add_subdirectory(math)
  # 收集可选库地址存于EXTRA_LIBS
  set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

# 查找当前目录下的所有源文件,并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 指定生成目标
add_executable(Demo ${DIR_SRCS})

# 添加链接库
target_link_libraries(Demo ${EXTRA_LIBS})

这时我们需要生成的可执行文件Demo是由DIR_SRCS目录中的源文件生成,并与EXTRA_LIBS目录下的库进行连接。

配置文件config.h并不需要我们直接编写,我们需要编写一个config.h.in文件,来让CMake自动生成config,h,内容如下:

#cmakedefine USE_MYMATH

工程目录结构如下:

~/Demo_4
    |
    +--CMakeList.txt
    +--main.cpp
    +--config.h.in
    +--math/
        |
        +--CMakeList.txt
        +--MathFunctions.cpp
        +--MathFunctions.h

之后我们需要修改我们的源文件main.cpp,我们需要引用新的配置头文件config.h,让其根据USE_MYMATH的预定义值来选择调用MathFunctions库还是标准库。

修改后的main.cpp文件:

#include <iostream>
// #include "math/MathFunctions.h"
#include "config.h"

#ifdef USE_MYMATH
    #include "math/MathFunctions.h"
#else
    #include<math.h>
#endif

using namespace std;

int main(int argc, char *argv[])
{
    if (argc < 3){
        cout << "Usage: " << argv[0] << "base exponent" <<endl;
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);

#ifdef USE_MYMATH
    cout << "Using our own math lib. "<<endl;
    double result = power(base, exponent);
#else
    cout << "Using standard math lib. "<<endl;
    double result = pow(base, exponent);
#endif
    cout << base << "^" << exponent << " is " << result <<endl;
    return 0;
}

编译一下:

shi@shi-Z370M-S01:~/Demo_4$ cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/shi/Demo_4
shi@shi-Z370M-S01:~/Demo_4$ make
Scanning dependencies of target MathFunctions
[ 25%] Building CXX object math/CMakeFiles/MathFunctions.dir/MathFunctions.cpp.o
[ 50%] Linking CXX static library libMathFunctions.a
[ 50%] Built target MathFunctions
Scanning dependencies of target Demo
[ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o
[100%] Linking CXX executable Demo
[100%] Built target Demo
shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10
Using standard math lib. 
2^10 is 1024

这里出现了一个问题,用默认的cmake命令编译之后,执行可执行文件Demo时发现,并没有引用我们自己编写的库,而是引用了标准库。

之后检查所有文件没有发现问题,安装带gui的CMake后,执行ccmake命令,在gui中显示USE_MYMATH正常的被配置为ON,执行编译后运行:

shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10
Using our own math lib. 
2^10 is 1024

结果正常调用了自己编写的库,目前原因未知。(2019.10.21)

参考:

  1. CMake 入门实战 - Hahack.com
  2. CMake简易入门 - cnblogs