C/C++ 的构建/编译工具有很多,CMake应该属于第三代构建工具,其次个人觉得应该是C++生态领域中最广的,一些新一代的虽好但是生态不行!Cmake-Demo地址: https://github.com/Anthony-Dong/cpp
介绍 C/C++ 的构建/编译工具有很多,CMake应该属于第三代构建工具,其次个人觉得应该是C++生态领域中最广的,一些新一代的虽好但是生态不行!
第一代:g++/gcc/clang,最原始了,构建便大点的项目非常痛苦
第二代:Makefile,通过Makefile语法规则简化了命令
第三代:Autotools 和 CMake 其实就是省略了自己写Makefile的过程!
第四代:Bazel 、Blade 集大成者支持任何语言的构建,对于构建缓存支持也好!
注意 :Bazel生态不太行,但是语法比较现代化,不过可以通过 repo_rule 的方式引入cmake项目,但是缺陷就是需要将cmake项目编译成静态库再依赖!
快速开始 main.cpp
文件1 2 3 4 5 6 7 #include <iostream> #include <fmt/core.h> int main () { std::cout << "hello world!" << std::endl; fmt::print ("hello {}!\n" , "world" ); }
CMakeLists.txt
文件1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cmake_minimum_required (VERSION 3.8 .0 )project (cmake_demo)set (CMAKE_CXX_STANDARD 11 )set (CMAKE_CXX_COMPILER g++)link_directories (/usr/local/lib)add_executable (${PROJECT_NAME} main.cpp)target_include_directories (${PROJECT_NAME} PRIVATE /usr/local/include )target_link_libraries (${PROJECT_NAME} PUBLIC fmt)
执行 cmake .
即可编译 备注: fmt库可以前往 fmt 官网自行下载构建 https://fmt.dev/latest/usage.html 关键概念 其他注意事项可以看: https://github.com/anthony-dong/cpp/blob/master/doc/cmake_notes.md
target Cmake的所有操作都是围绕着 target
走了, target
可以是 可执行文件(executable)、静态链接库(static)、动态链接库(shared) !了解了这些你会很方便的理解Camke的编写方式!
1 2 3 4 5 6 7 8 add_executable (demo make.cpp)add_library (lib_utils SHARED utils.cpp)add_library (${PROJECT_NAME} STATIC random.cpp)
target_xxx_directories 我建议申明为 PRIVATE
,遵循使用再引用的原则
1 2 3 4 5 target_include_directories (${PROJECT_NAME} PRIVATE /usr/local/include )target_link_directories (${PROJECT_NAME} PRIVATE /usr/local/lib)
target_link_libraries 这里表示我引用的链接库,这里我建议设置为 PUBLIC
,不然别人引用你的库会发现找不到某个函数的定义,那就尴尬了,报错!
如果有比较好的规范的话我觉得无所谓!
1 2 3 4 target_link_directories (${PROJECT_NAME} PRIVATE /usr/local/lib)target_link_libraries (${PROJECT_NAME} PUBLIC fmt)
多模块管理 - 本地链接 一般情况下,如果你是自己本地学习,本地链接是最方便的因为啥了不需要重复编译哇,一次编译处处使用!
这里我大概就自己写了个demo,大家自行参考,地址:https://github.com/Anthony-Dong/cmake_demo
学会用cmake自己安装链接库,这里的例子是protoc 这里其实很简单,首先这些库一般对于cmake都支持,其次就是找到介绍文件,看看它的CamkeLists.txt
文件,最后自己看一下哪些需要哪些不需要,最后自己编译即可!具体可以看: https://github.com/Anthony-Dong/cmake_demo
使用 protoc 库 1 2 3 4 5 6 7 8 project (lib_idl)add_library (${PROJECT_NAME} SHARED model.pb.cc common.pb.cc utils.cpp)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)
定义pb文件,使用protobuf可以省去了自己写序列化函数的时间,执行命令 protoc -I . --cpp_out=. common.proto model.proto
注意: protoc版本必须要和链接库版本一致,不然项目编译报错!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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 ;}syntax = "proto2" ; package model.idl;enum Status { On = 0 ; Off = 1 ; }
主函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #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 比较现代化吧,比较推荐管理外部模块!
CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 include (FetchContent)if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0" ) cmake_policy (SET CMP0135 NEW) endif ()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)
main.cpp 1 2 3 4 5 6 #include "spdlog/spdlog.h" int main () { spdlog::info ("hello {}" , "cmake" ); SPDLOG_INFO ("hello {}" , "cmake" ); }
多模块管理 - sub_module sub_module
比较现代化,比较推荐管理子模块,很多cmake项目是全局一个cmakelists.txt 文件维护构建信息,导致庞大。sub_module 可以添加子模块的方式去解决!具体可以参考这个项目: https://github.com/Anthony-Dong/cpp
add_subdirectory
仅需要模块目录定义 CMakeLists.txt
文件即可!
CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 cmake_minimum_required (VERSION 3.11 )include (cmake/cc_library.cmake)include (cmake/cc_binary.cmake)include (cmake/cc_test.cmake)add_subdirectory (cpp/io)add_subdirectory (cpp/utils)add_subdirectory (cpp/network)add_subdirectory (cpp/log)add_subdirectory (example)add_subdirectory (test )
cpp/io/CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 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 )
远程调试 远程调试的能力依赖于gdbserver,其次 cmake构建的时候需要指定 -DCMAKE_BUILD_TYPE=Debug
Debug:用于在没有优化的情况下,使用带有调试符号构建库或者可执行文件
1 2 3 4 5 6 ~/go/src/github.com/anthony-dong/cmake_demo gdbserver :10086 output/cmake_demo Process output/cmake_demo created; pid = 149849 Listening on port 10086 Remote debugging from host 10.78.117.128 now 2023-08-18 17:28:22 hello world!
2. 效果
cmake 构建命令 Makefile 中定义了 build
和 test
指令,也是cmake中场景的命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 .PHONY : all init build test cleanDIR_ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST) ) )) CC := clang CXX := clang++ CXX_STANDARD := 17 C_FLAGS := CXX_FLAGS := -pthread BUILD_TYPE := Debug BUILD_DIRECTORY := output all: build init: mkdir -p "$(BUILD_DIRECTORY) " mkdir -p "$(BUILD_DIRECTORY) /install" cmake --log-level=DEBUG -G "Unix Makefiles" -DCMAKE_BUILD_TYPE="$(BUILD_TYPE) " \ -DCMAKE_INSTALL_PREFIX="$(DIR_ROOT) $(BUILD_DIRECTORY) /install" \ -DCMAKE_C_COMPILER="$(CC) " \ -DCMAKE_CXX_COMPILER="$(CXX) " \ -DCMAKE_C_FLAGS="$(C_FLAGS) " \ -DCMAKE_CXX_FLAGS="$(CXX_FLAGS) " \ -DCMAKE_CXX_STANDARD="$(CXX_STANDARD) " \ -DABSL_PROPAGATE_CXX_STD=ON \ -S . \ -B "$(BUILD_DIRECTORY) " build: init cmake --build "$(BUILD_DIRECTORY) " --config "$(BUILD_TYPE) " -j8 test: cd "$(BUILD_DIRECTORY) " && ctest --config "$(BUILD_TYPE) " --tests-regex '_test' -j8 clean: rm -rf "$(BUILD_DIRECTORY) "
Bazel 代码示例: https://github.com/Anthony-Dong/bazel_demo
最佳实践:https://anthony-dong.github.io/2023/08/20/1f843b2e9cdc2e9eb8812867e097b659/
Bazel Rule 的话我后期后有问题去写,暂时还没写完!