[Ubuntu] 无论如何都能学明白的Cmake入门

目录

  • 1 CMakeLists.txt 怎么写
    • 1.1 语句含义
    • 1.2 一些实例
      • 1.2.1 创建一个可执行文件
      • 1.2.2 引用.h文件
      • 1.2.3 创建静态库和共享库
      • 1.2.3 使用自定义库
  • 2 一点很方便的sh脚本

1 CMakeLists.txt 怎么写

1.1 语句含义

cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。
CMakeLists.txt直接指定了程序的编译规则。
因为仅仅只是作为一个入门学习。我们的目标是

  • 完成软件编译
  • 完成静态库/共享库的建立
  • 使用静态库/共享库

首先我们需要了解CMakeLists.txt中最常用的语句,注意,以下内容对于大小写并没有严格的要求,可以按照个人喜好修改:

  1. 指定cmake的最低版本,通常情况下它并没有那么重要
cmake_minimum_required(VERSION 3.0.2)
  1. 设置项目名称,运行这一句之后就会自动生成两个变量:${PROJECT_NAME}与${PROJECT_SOURCE_DIR},分别指的是项目名称与CMakeLists.txt所在目录
project(YOYO)
  1. 变量设置,前一个参数为变量名,后一个参数为变量指代的路径,例如下面这个例子中就用EXECUTABLE_OUTPUT_PATH变量代表了${PROJECT_BINARY_DIR}/bin路径
set(EXECUTABLE_OUTPUT_PATH ${
     PROJECT_BINARY_DIR}/bin)
  1. 将目录下所有文件给到变量中,值得注意的是这里面的变量可以包含多个路径
aux_source_directory(
    ./src/shared/src SHARED_SRC
)
  1. 设置子目录,下面这个例子中就把当前目录下的src目录指定成了子目录,这个语句会执行指定子目录的CMakeLists.txt,并且子目录的CMakeLists.txt中可以使用父目录CMakeLists.txt中设置的变量
add_subdirectory(./src)
  1. 设定头文件的路径,这样可执行文件就能够#include到这里指定路径下的头文件
include_directories(
   ./include
)
  1. 设置可执行文件,下面的例子中将src目录下的main.cpp设置成了可执行文件,生成的可执行文件名为${PROJECT_NAME}
add_executable( ${
     PROJECT_NAME}
    ./src/main.cpp
)
  1. 设置链接库的路径
link_directories(
	/path/lib
)
  1. 指定要使用的库,在linux中,.a文件是静态库,.so文件是共享库,也称动态库
target_link_libraries(${
     PROJECT_NAME}
    libname.a
    libname.so
)
  1. 将文件打包成链接库,第一个参数是链接库的名称(例如耳熟能详的OpenCV),第二个参数是链接库类型,可以是STATIC(静态库)、SHARED(共享库)、PUBLIC(俺不知道了,公共库吗?),第三个参数是打包成链接库的文件,可以用aux_source_directory命令弄出一大堆文件然后一起输进去
add_library(LIB_NAME SHARED ${
     SHARED_SRC})

1.2 一些实例

当我自己查资料学习的时候看了大量的语句说明,并不能很好地理解,其实在实例中逐步了解效果会好很多,所以这里我们尝试给出一些实例。

1.2.1 创建一个可执行文件

  1. 我们在工作目录下创建一个src文件夹用于存放cpp文件,build文件夹作为二进制目录(就理解成装cmake产生的文件吧),再创建一个CMakeLists.txt
.
├── build
├── CMakeLists.txt
└── src
    └── main.cpp
其中main.cpp文件是一个最简单的hello world程序。CMakeLists.txt内容如下
cmake_minimum_required(VERSION 3.0.2)
project(EXAMPLE)

set(EXECUTABLE_OUTPUT_PATH ${
     PROJECT_BINARY_DIR}/bin)

add_executable(${
     PROJECT_NAME}
    ${
     PROJECT_SOURCE_DIR}/src/main.cpp
)
2 在指定了最小版本、项目名以后,我们先将可执行文件的输出路径指定成${PROJECT_BINARY_DIR}/bin
其中${PROJECT_BINARY_DIR}就是指的build文件夹

然后我们设定src目录下的main.cpp为可执行文件

使用以下命令进行编译
cd ./build
cmake ..
make
其实cmake命令后面的参数指的是CMakeLists.txt的路径,cmake将生成的文件保存在当前目录下
所以如果直接在工作空间下运行会显得很杂乱,习惯上都在build目录下执行cmake ..
  1. 现在我们就能够在build/bin目录下看到可执行文件EXAMPLE,直接运行就能看到执行结果
 grandpadzb $ ~/cpp/learn_cmake/build/bin  ./EXAMPLE
hello world

1.2.2 引用.h文件

.
├── build
├── CMakeLists.txt
├── include
│   └── yoyo.h
└── src
    └── main.cpp
  1. 在样例中我们创建了yoyo.h文件,希望main.cpp可以通过#include 来执行之中的函数。所以只需要在CMakeLists.txt中添加一句话,增加include_directories的一个路径即可。
cmake_minimum_required(VERSION 3.0.2)
project(EXAMPLE)

set(EXECUTABLE_OUTPUT_PATH ${
     PROJECT_BINARY_DIR}/bin)

include_directories(
    ${
     PROJECT_SOURCE_DIR}/include
)

add_executable(${
     PROJECT_NAME}
    ${
     PROJECT_SOURCE_DIR}/src/main.cpp
)
  1. 同样在build目录下分别执行cmake … 和make命令就可以生成可执行文件。这么做之前最好把build里面之前生成的文件删光

1.2.3 创建静态库和共享库

.
├── build
├── CMakeLists.txt
└── src
    ├── shared
    │   ├── CMakeLists.txt
    │   ├── include
    │   │   └── Pet.h
    │   └── src
    │       └── Pet.cpp
    └── static
        ├── CMakeLists.txt
        ├── include
        │   └── Pet.h
        └── src
            └── Pet.cpp
  1. 范例中我们先创建如上图的层级目录,其中src/shared与src/static目录分别存放共享库和静态库的文件。
  2. 我们以静态库为例编写静态库文件。内容非常简单,象征性表示了一下平时我们在h文件中写函数然后在cpp文件中实现的一个场景。
// Pet.h
#include 
using namespace std;

#define CAT "miaomiao"
#define DOG "wangwang"

class Pet
{
     
private:
    /* data */
public:
    int type = 0;
    void say();
    Pet(int type = 0);
    ~Pet();
};
// Pet.cpp
#include "Pet.h"

Pet::Pet(int type){
     
    if(type == 0){
     
        this->type = 0;
    }
    else if(type == 1){
     
        this->type = 1;
    }
    else{
     
        this->type = -1;
    }
}

Pet::~Pet(){
     }

void Pet::say(){
     
    switch (this->type)
    {
     
    case 0:{
     
        cout << "Pet says: " << CAT << endl;
        break;
    }
    case 1:{
     
        cout << "Pet says: " << DOG << endl;
        break;
    }
    default:
        cout << "Pet says: %#&*%@@$#" << endl;
        break;
    }
}
  1. 编写静态库的CMakeLists.txt。首先我们设置了库文件的输出路径为build/lib目录,所以编译结束之后我们会在该目录下看到.a文件,接下来把src目录下的所有cpp文件路径创建成变量STATIC_SRC,并且让它们能够include到include目录下的头文件。最后将这些文件建立成名叫Pet_static的静态库,add_library中的STATIC换成SHARED就变成共享库了。
cmake_minimum_required(VERSION 3.0.2)

SET(LIBRARY_OUTPUT_PATH ${
     PROJECT_BINARY_DIR}/lib)
AUX_SOURCE_DIRECTORY(
        ${
     PROJECT_SOURCE_DIR}/src/static/src STATIC_SRC
    )
INCLUDE_DIRECTORIES(
        ${
     PROJECT_SOURCE_DIR}/src/static/include
    )

add_library(Pet_static STATIC ${
     STATIC_SRC})
  1. 在3中可能会产生疑问,没有写project,怎么会有${PROJECT_BINARY_DIR}等变量存在呢?其实是把project写在了最开头目录下的CMakeLists.txt中。见下图。
    下图中我们首先定义了project为Pet,然后设置了src/shared与src/static为子目录,这使得这两个目录下的CMakeLists.txt文件可以使用这里的CMakeLists.txt中,ADD_SUBDIRECTORY命令之前建立的变量。
cmake_minimum_required(VERSION 3.0.1)
project(Pet)
ADD_SUBDIRECTORY(src/shared)
ADD_SUBDIRECTORY(src/static)
  1. 共享库的建立方式基本一致,只需要在add_library中将类型参数写为SHARED就可以了。
  2. 同样是在build文件夹下执行cmake …与make命令,然后就可以在build/lib文件夹下看到库文件了(.a文件是静态库,.so文件是共享库),文件名是lib{在add_library中定义的名字}.a或lib{在add_library中定义的名字}.so

1.2.3 使用自定义库

  1. 回到我们之前创建的工作目录下,把CMakeLists.txt修改为以下内容。在之前的基础上,增加了link_libraries提供链接库的路径,由于要使用库的头文件,所以要在include_directories中增加一条库的include文件夹路径。增加target_link_libraries给当前项目添加静态库。
    注意,以上三个路径不要使用~来表示/home/username,我开始很喜欢这么写,但是会报错的
cmake_minimum_required(VERSION 3.0.2)
project(EXAMPLE)

set(EXECUTABLE_OUTPUT_PATH ${
     PROJECT_BINARY_DIR}/bin)

link_libraries(
    /home/username/cpp/Pet/build/lib
)
include_directories(
    ${
     PROJECT_SOURCE_DIR}/include
    /home/username/cpp/Pet/src/static/include
)

add_executable(${
     PROJECT_NAME}
    ${
     PROJECT_SOURCE_DIR}/src/main.cpp
)
target_link_libraries(${
     PROJECT_NAME}
    /home/username/cpp/Pet/build/lib/libPet_static.a
)
  1. 在main.cpp文件中使用#include 调用函数。
  2. 仍然在build文件夹下使用cmake … 与make命令,使用生成的可执行文件结果如下。
 grandpadzb  ~/cpp/learn_cmake/build/bin  ./EXAMPLE 
Pet says: %#&*%@@$#

2 一点很方便的sh脚本

如果每次建立工程文件或者库文件都要像这样不断新建文件夹,重写CMakeLists.txt实在是太麻烦啦。我就根据自己的偏好写了两个sh脚本,内容如下:

首先在~/下创建backups目录,新建文件build_lib.sh、build_ws.sh、CMakeLists.txt

  1. build_lib.sh:
#! /bin/sh
mkdir build
mkdir src
touch CMakeLists.txt
cat >>./CMakeLists.txt<(VERSION 3.0.1)
project(PROJECT_NAME)

#ADD_SUBDIRECTORY(src/shared)
#ADD_SUBDIRECTORY(src/static)
EOF
mkdir src/static
mkdir src/static/src
mkdir src/static/include
touch src/static/CMakeLists.txt
cat >> src/static/CMakeLists.txt << EOF
cmake_minimum_required(VERSION 3.0.2)
SET(LIBRARY_OUTPUT_PATH \${
     PROJECT_BINARY_DIR}/lib)
AUX_SOURCE_DIRECTORY(
        \${
     PROJECT_SOURCE_DIR}/src/static/src STATIC_SRC
    )
INCLUDE_DIRECTORIES(
        \${
     PROJECT_SOURCE_DIR}/src/static/include
    )
add_library(LIB_NAME STATIC \${
     STATIC_SRC})
EOF

mkdir src/shared
mkdir src/shared/src
mkdir src/shared/include
touch src/shared/CMakeLists.txt
cat >> src/shared/CMakeLists.txt << EOF
cmake_minimum_required(VERSION 3.0.2)
SET(LIBRARY_OUTPUT_PATH \${
     PROJECT_BINARY_DIR}/lib)
AUX_SOURCE_DIRECTORY(
        \${
     PROJECT_SOURCE_DIR}/src/shared/src SHARED_SRC
    )
INCLUDE_DIRECTORIES(
        \${
     PROJECT_SOURCE_DIR}/src/shared/include
    )
add_library(LIB_NAME SHARED \${
     SHARED_SRC})
EOF

touch readme.md
cat >> readme.md << EOF
当前目录下的CMakeLists.txt中注释了指定子目录的两条命令,根据需要创建静态库还是共享库来解注释使用,并修改PROJECT_NAME

SHARED或者STATIC文件夹下的CMakeLists.txt只需填写库名称
将函数头文件写在include文件夹中,函数实现写在src文件夹中

请在build目录下使用
cmake ..
make
EOF
tree
  1. build_ws.sh
#! /bin/sh

mkdir build
mkdir include
mkdir src

cp ~/backups/CMakeLists.txt .
touch readme.md
cat >> ./readme.md << EOF
需在当前目录下CMakeLists.txt中补全
PROJECT_NAME
FILE_NAME (可执行文件名)
其他根据需求解注释使用

请在build目录下进行
cmake ..
make

生成的可执行文件在build/bin目录下
EOF
tree
  1. CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)

# Create ${PROJECT_NAME} ${PROJECT_SOURCE_DIR}
project(PROJECT_NAME)

# Set executable output path
set(EXECUTABLE_OUTPUT_PATH ${
     PROJECT_BINARY_DIR}/bin)

# Add child_dir if you need
#add_subdirectory(./src)
#add_subdirectory(./include)

# Set include path ( contains lib include path)
include_directories(
    ./include
)

# Set lib path
#link_directories({lib_path})

add_executable(${
     PROJECT_NAME}
    ./src/FILE_NAME
)

# Set link libs if you need
#target_link_libraries(${PROJECT_NAME}
#    libname.a
#    libname.so
#)

具体含义就不过多介绍了,因为我写的并不好,仅仅是临时有了这个想法为了实现这个功能临时了解了几个相关的语法就动手了,所以它能够用,但是很丑。
只要运行这两个sh文件就能在当前目录下创建出上面例子里面使用的目录结构还有合适的CMakeLists.txt,根据readme.md里面的简要介绍去稍微改一下CMakeLists.txt就可以直接去编译了。

由于我使用的是zsh,所以还在.zshrc中添加了如下几行,用更简单的命令去创建简化。

alias cmkl='cp ~/backups/CMakeLists.txt .'
alias build_lib='cp ~/backups/build_lib.sh .'
alias build_ws='cp ~/backups/build_ws.sh .'

还有很难受的事,当我把这俩脚本写完了之后,惊喜地发现vscode完全具有相关拓展可以更漂亮地完成这些工作。但是最后由于我还是更习惯自己写的那种目录结构,所以暂时还是用着自己的那一套了。
如果你需要快速建立CMakeLists.txt目录,请直接去学习vscode的相关拓展!

你可能感兴趣的:(Ubuntu相关)