CMake学习

1,104 阅读2分钟

C/C++ 的构建/编译工具有很多,CMake应该属于第三代构建工具,其次个人觉得应该是C++生态领域中最广的,一些新一代的虽好但是生态不行!Cmake-Demo地址: github.com/Anthony-Don…

介绍

C/C++ 的构建/编译工具有很多,CMake应该属于第三代构建工具,其次个人觉得应该是C++生态领域中最广的,一些新一代的虽好但是生态不行!

第一代:g++/gcc/clang,最原始了,构建便大点的项目非常痛苦

第二代:Makefile,通过Makefile语法规则简化了命令

第三代:AutotoolsCMake 其实就是省略了自己写Makefile的过程!

第四代:BazelBlade 集大成者支持任何语言的构建,对于构建缓存支持也好!

快速开始

  1. main.cpp 文件
#include <iostream>
#include <fmt/core.h>

int main() {
    std::cout << "hello world!" << std::endl;
    fmt::print("hello {}!\n", "world");
}
  1. CMakeLists.txt 文件
# 设置CMake最低版本
cmake_minimum_required(VERSION 3.8.0)

# 设置项目名称
project(cmake_demo)

## 设置C++版本
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) # force stdc++=20
set(CMAKE_CXX_EXTENSIONS OFF) # 禁止使用GUN特性

## 配置clang
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi -pthread")
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++ -lc++abi -pthread")
endif ()

## 设置编译选项
## add_compile_options(-Wall -Wextra -pedantic -Werror)

## 设置全局动态链接库目录
link_directories(/usr/local/lib)

## 设置target 为一个可执行文件
add_executable(${PROJECT_NAME} main.cpp)
## 引用的头文件地址
target_include_directories(${PROJECT_NAME} PRIVATE /usr/local/include)
## 引用的链接库
target_link_libraries(${PROJECT_NAME} PUBLIC fmt)
  1. 执行 cmake . 即可编译
  2. 备注: fmt库可以前往官网自行下载构建 fmt.dev/latest/usag…

关键概念

其他注意事项可以看: github.com/anthony-don…

target

Cmake的所有操作都是围绕着 target 走了, target 可以是 可执行文件(executable)、静态链接库(static)、动态链接库(shared) !了解了这些你会很方便的理解Camke的编写方式!

# 定义可执行文件: targe为demo
add_executable(demo make.cpp)

# 定义动态链接库: targe为lib_utils
add_library(lib_utils SHARED utils.cpp)

# 定义静态链接库: targe为lib_random
add_library(${PROJECT_NAME} STATIC random.cpp)

备注:INTERFACE 主要是用于那种全部是头文件的构建目标!

target_xxx_directories

# 头文件引用地址
target_include_directories(${PROJECT_NAME} PRIVATE /usr/local/include)

# 动态链接库lookup的地址
target_link_directories(${PROJECT_NAME} PRIVATE /usr/local/lib)

target_link_libraries

这里表示我引用的链接库,这里我建议设置为 PUBLIC ,不然别人引用你的库会发现找不到某个函数的定义,那就尴尬了,报错!

如果有比较好的规范的话我觉得无所谓!

target_link_directories(${PROJECT_NAME} PRIVATE /usr/local/lib)

# 引用的链接库
target_link_libraries(${PROJECT_NAME} PUBLIC fmt)

多模块管理 - 本地链接

一般情况下,如果你是自己本地学习,本地链接是最方便的因为啥了不需要重复编译哇,一次编译处处使用!

这里我大概就自己写了个demo,大家自行参考,地址:github.com/Anthony-Don…

  1. 学会用cmake自己安装链接库,这里的例子是protoc

​ 这里其实很简单,首先这些库一般对于cmake都支持,其次就是找到介绍文件,看看它的CamkeLists.txt 文件,最后自己看一下哪些需要哪些不需要,最后自己编译即可!具体可以看: github.com/Anthony-Don…

  1. 使用 protoc 库
project(lib_idl)

add_library(${PROJECT_NAME} SHARED model.pb.cc common.pb.cc utils.cpp)

## link protobuf
target_include_directories(${PROJECT_NAME} PRIVATE /usr/local/include)
target_link_directories(${PROJECT_NAME} PRIVATE /usr/local/lib)
target_link_libraries(${PROJECT_NAME} PUBLIC protobuf)
  1. 定义pb文件,使用protobuf可以省去了自己写序列化函数的时间,执行命令 protoc -I . --cpp_out=. common.proto model.proto

注意: protoc版本必须要和链接库版本一致,不然项目编译报错!

//  model.proto
syntax = "proto2";

package model.idl;

import "common.proto";

enum Gendor {
  Female = 1;
  Male = 2;
}

message People {
  optional int64 ID = 1;
  optional string Name = 2;
  optional Gendor Gendor = 3;
  repeated string ExtralList = 4;
  map<string, string> Extra = 5;
  optional ExtraInfo ExtraInfo = 6;
  optional Status status = 7;
}

message ExtraInfo {optional string name = 1;}


// common.proto
syntax = "proto2";

package model.idl;

enum Status {
  On = 0;
  Off = 1;
}
  1. 主函数
#include <fmt/core.h>
#include <idl/model.pb.h>
#include <idl/utils.h>

#include <db/Class.h++>
#include <iostream>

int main() {
    std::cout << "hello world!" << std::endl;
    fmt::print("hello {}!\n", "world");

    DB::Model::Class a(1, "1314班");
    fmt::print("id: {}, name: {}\n", a.getId(), a.getName());

    model::idl::People people{};
    people.set_name("tom");
    people.set_id(1);
    fmt::print("people: {}\n", model::idl::toJson(people));
    fmt::print("people: {}\n", *model::idl::toJsonPtr(people));
}

多模块管理 - FetchContent

FetchContent 比较现代化吧,比较推荐管理外部模块!注意很多时候项目并不是一个camke项目,那么你需要自己把这个项目包装成一个cmake项目,参考这个实现 github.com/kingsamchen…

  1. CMakeLists.txt
include (FetchContent)

if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
    cmake_policy (SET CMP0135 NEW)
endif ()

set(FETCHCONTENT_TRY_FIND_PACKAGE_MODE NEVER) #强制使用远程模块

FetchContent_Declare (
        spdlog
        URL https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz
)

FetchContent_MakeAvailable (spdlog)

add_library(main main.cpp)
target_link_libraries(main PUBLIC spdlog)
  1. main.cpp
#include "spdlog/spdlog.h"

int main() {
    spdlog::info("hello {}", "cmake");
    SPDLOG_INFO("hello {}", "cmake");
}

多模块管理 - sub_module

sub_module 比较好的是可以将一个项目拆分成多个子模块方便管理,不然一个 CMakeList.txt 文件管理整个项目复杂度会高一些!具体可以参考这个项目: github.com/Anthony-Don…

  1. CMakeLists.txt
cmake_minimum_required (VERSION 3.11)

add_subdirectory (cpp/io) # 引入子模块
  1. cpp/io/CMakeLists.txt 模块内部自己再管理构建目标
cc_library (
        NAME cpp_io
        ALIAS cpp::io
        SRCS io.cpp
        HDRS io.h
        DEPS cpp::utils
)

cc_test (
        NAME cpp_io_test
        SRCS io_test.cpp
        DEPS cpp::io
)

vscode + cmake 开发环境

我个人感觉 vscode 开发起来比较舒服,可能是我电脑垃圾带不动 clion!

vscode + clangd(代码提示还是很不错的) 环境非常的简单,只需要下载几个插件

备注: vscode 非常方便进行远程开发,只需要下载个插件 Remote - SSH 即可!

  1. clangd marketplace.visualstudio.com/items?itemN…
  2. codelldb: marketplace.visualstudio.com/items?itemN…
  3. cmake tools: marketplace.visualstudio.com/items?itemN…
  4. cmake: marketplace.visualstudio.com/items?itemN…
  5. .vscode/settings.json 配置
{
  
  // clangd 配置
  // clangd config: https://github.com/clangd/vscode-clangd
  "clangd.path": "/Users/bytedance/software/clangd_18.1.3/bin/clangd", // 推荐选择最新的clangd下载, 前往这里下载 https://github.com/clangd/clangd/releases
  "clangd.arguments": [
    "--completion-style=detailed", // 更加详细的信息吧,不希望的可以配置bundled
    "--background-index",
		"--fallback-style=Google", // 根据自己需求
    "--clang-tidy",
    "-j=2",
    "--pch-storage=disk", // 降低内存开销,能降低2/3左右的内存
    "--log=error",
    "--pretty",
    "--compile-commands-dir=output"
  ],
  
  // cmake 配置
  // https://github.com/microsoft/vscode-cmake-tools/blob/main/docs/cmake-settings.md
  "cmake.buildArgs": [],
  "cmake.configureArgs": [
    "-DCMAKE_EXPORT_COMPILE_COMMANDS=1", // 注意(必须开启): enable export clangd compile_commands.json
    "-DCMAKE_BUILD_TYPE=Debug", // debug
    "-DCMAKE_C_COMPILER=/usr/local/opt/llvm@14/bin/clang", // cc
    "-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@14/bin/clang++" // cxx
  ],
  "cmake.generator": "Unix Makefiles",
  "cmake.buildDirectory": "${workspaceFolder}/output",
  "cmake.debugConfig": {
    // debug使用lldb, 其实就是配置launch.json配置
    "type": "lldb",
    "request": "launch",
    "program": "${command:cmake.launchTargetPath}",
    "args": [],
    "cwd": "${workspaceFolder}"
  } 
}
  1. 选择构建目标

img

  1. 运行构建目标

img

Bazel

代码示例: github.com/Anthony-Don…

最佳实践:anthony-dong.github.io/2023/08/20/…

Bazel Rule 的话我后期后有问题去写,暂时还没写完!