brpc

编译

1
2
3
4
5
$ bash config_brpc.sh --headers="/path/to/deps/include/" --libs="/path/to/deps/lib/" --with-glog --with-thrift
$ make -j ${make_thread_num}
$ cp -a output/include/* "/path/to/install/include/"
$ cp -a output/lib/* "/path/to/install/lib/"
$ cp -a output/bin/* "/path/to/install/lib/"

问题

NO 1

描述

1
2
3
4
5
6
ERROR: something wrong with flag 'flagfile' in file '/home/wii/Git/cooking/c++/universal/third/build/gflags-2.2.2/src/gflags.cc'.  One possibility: file '/home/wii/Git/cooking/c++/universal/third/build/gflags-2.2.2/src/gflags.cc' is being linked both statically and dynamically into this executable.

或者

Ignoring RegisterValidateFunction() for flag pointer 0x7f3d0b54893c: no flag found at that address
...

解决

gflags静态库和共享库同时存在导致。gflags 是选择手动编译,在编译时为cmake指定-DBUILD_SHARED_LIBS=1只编译共享库,如果是使用命令安装,可以尝试先移除/usr/local/lib/libgflag.a/usr/local/lib/libgflag_nothread.a

gflags

编译

1
2
3
4
$ mkdir build && cd build
$ cmake -DCMAKE_INSTALL_PREFIX:PATH="/path/to/install" -DBUILD_SHARED_LIBS=1 .. # 编译共享库,如需打包静态库 添加 -DBUILD_STATIC_LIBS=1;默认打包静态库,指定打包共享库不打包静态库,可同时指定
# -DGFLAGS_NAMESPACE=google 指定 namespace 名称,默认gflags
$ make -j4 && make install

更多参考这里

CMake

提取Namespace

1
2
3
4
5
6
7
8
9
10
11
12
execute_process(
COMMAND bash -c "grep \"namespace [_A-Za-z0-9]\\+ {\" ${DEPS_INCLUDE_DIR}/gflags/gflags_declare.h | head -1 | awk '{print $2}' | tr -d '\n'"
OUTPUT_VARIABLE GFLAGS_NS
)

if (${GFLAGS_NS} STREQUAL "GFLAGS_NAMESPACE")
execute_process(
COMMAND bash -c "grep \"#define GFLAGS_NAMESPACE [_A-Za-z0-9]\\+\" ${DEPS_INCLUDE_DIR}/gflags/gflags_declare.h | head -1 | awk '{print $3}' | tr -d '\n'"
OUTPUT_VARIABLE GFLAGS_NS
)
endif ()
message("gflags namespace: " ${GFLAGS_NS})

版本信息

CMakeLists.txt 添加版本定义

1
2
3
4
5
6
7
8
9
execute_process(
COMMAND sh -c "git describe --exact-match --tags 2> /dev/null || git rev-parse --short HEAD"
OUTPUT_VARIABLE TAG_REVERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (TAG_REVERSION STREQUAL "")
set(TAG_REVERSION "UNKNOWN")
endif ()
add_definitions(-DTAG_REVERSION="${TAG_REVERSION}")

设置版本信息

1
2
3
4
int main(int argc, char *argv[]) {
google::SetVersionString(TAG_REVERSION); // 在比较早的位置设置
...
}

二进制程序打印版本信息

1
./bin/{app} --version

使用

类型

基于gflags的设计哲学,不提供复杂的数据类型,可自行解析。

  • DEFINE_bool
  • DEFINE_int32
  • DEFINE_int64
  • DEFINE_uint64
  • DEFINE_double
  • DEFINE_string

定义

示例

1
2
3
4
5
#include <gflags/gflags.h>

DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");
DEFINE_string(languages, "english,french,german",
"comma-separated list of languages to offer in the 'lang' menu");

说明

可在任意文件中定义flag,但只能定义一次(只能 DEFINE 一次,但是可 DECLARE 多次)。如果需要访问位于多个源文件中的glfag,那么在其他文件中使用 DECLARE。最好,在foo.cc 中使用 DEFINE 定义,在foo.h 中使用 DECLARE 声明,那么只要在代码中添加 #include <foo.h> 就可以使用该flag。

访问

1
2
3
4
5
6
7
// 修改
FLAGS_languages += ",klingon";

// 方法调用
if (FLAGS_languages.find("finnish") != string::npos) {
// do something
}

声明检查

1
2
3
4
5
6
7
8
static bool ValidatePort(const char* flagname, int32 value) {
if (value > 0 && value < 32768) // value is ok
return true;
printf("Invalid value for --%s: %d\n", flagname, (int)value);
return false;
}
DEFINE_int32(port, 0, "What port to listen on");
DEFINE_validator(port, &ValidatePort);

在全局初始化时(紧接着DEFINE_int32定义flag)注册检查方法,可以保证在运行main()函数解析命令号之前执行校验。上面的代码,使用DEFINE_validator 宏指令调用 RegisterFlagValidator 方法,如果注册成功,返回true;如果第一个参数不是命令行标记或者该标记注册了其他校验,则返回false;返回值以<flag>_validator_registered 命名作为全局变量供访问。

解析标记

1
gflags::ParseCommandLineFlags(&argc, &argv, true);

通常,改代码唯有main()的开始。argcargv 是传入main() 方法的准确参数,该例程可能会修改他们。最后的布尔参数用于指示是否从argc中删除标记(flags)以及其参数(arguments),并同步修改argv。如果为true,在该方法调用之后,argv只会保存命令行参数(commandline arguments),而不是命令行标记(glags)。如果为false,那么 ParseCommandLineFlags 不会修改 argc,但是会对argv中的参数重排序,使得所有的标记在最开始。比如,"/bin/foo" "arg1" "-q" "arg2",方法会重排序 argv 为 "/bin/foo" "-q" "arg1" "arg2",ParseCommandLineFlags 返回 argv 中第一个参数的索引,也即标记(flag)的后面的索引(在刚才的例子中,返回2,因为 argv[2] 指向第一个参数 arg1)。

,命令行标记(commandline glags)形如 -e -q,命令行参数(commandline arguments)形如 arg1 arg2

在命令行设置标记

1
$ foo --nobig_menu -languages="chinese,japanese,korean" ...

当运行 ParseCommandLineFlags 时,会设置 FLAGS_big_menu = false; FLAGS_languages = "chinese,japanese,korean"。通过在标记前面添加no,为标记设置为 false 值。以下方式均可设置 languages 值。

1
2
3
4
app_containing_foo --languages="chinese,japanese,korean"
app_containing_foo -languages="chinese,japanese,korean"
app_containing_foo --languages "chinese,japanese,korean"
app_containing_foo -languages "chinese,japanese,korean"

布尔型值稍有不同。

1
2
3
4
app_containing_foo --big_menu
app_containing_foo --nobig_menu
app_containing_foo --big_menu=true
app_containing_foo --big_menu=false

尽管有很多方式灵活设置标记值,但是推荐使用统一的格式 --variable=value,对于布尔值使用 --variable/--novariable

注意

  • 如果在命令行中指定了标记,但是没有在程序中定义(使用DEFINE),将会导致致命错误。可以通过添加 --undefok 来屏蔽该异常
  • 在 getopt 中,-- 标记会终止标记解析,所以 foo -f1 1 -- -f2 2,中,f1 是标记,f2 不是
  • 如果标记被指定多次,后面的值覆盖前面的,即最后一次定义有效
  • 不像 getopt 库那样支持 单字母同义词,比如 -h (和 --help 同义),同样的也不支持组合标记,如 ls -la

修改默认值

有时候标记位于依赖库中,如果只想在一个程序中修改默认值,其他应用不变。可在 main() 方法中赋值新值,在调用 ParseCommandLineFlags 之前。

1
2
3
4
5
DECLARE_bool(lib_verbose);   // mylib has a lib_verbose flag, default is false
int main(int argc, char** argv) {
FLAGS_lib_verbose = true; // in my app, I want a verbose lib by default
ParseCommandLineFlags(...);
}

这种情况下,用户依然可以在命令行中指定标记值,但是如果不指定,则使用新的默认值。

特殊标记

  • --undefok=flagname,flagname,...

    • 对于指定的这些标记名称,用于阻止当命令行指定标记但在程序中未定义导致的异常退出
  • --fromenv

    • --fromenv=foo,bar

    • 从环境变量中读取值,如果环境变量中未定义,则会导致致命异常

    1
    2
    3
    4
    5
    6
    export FLAGS_foo=xxx; export FLAGS_bar=yyy   # sh
    setenv FLAGS_foo xxx; setenv FLAGS_bar yyy # tcsh

    # 等同于

    --foo=xxx, --bar=yyy
  • --tryfromenv

    • 类似于 --fromenv ,区别是如果环境变量中未定义标记,不会导致异常
  • --flagfile

    • --flagfile=f

    • 从文件读取参数定义,文件内容为标记定义列表,每行一个

    • 与命令行不同的是,需要等号将标记和参数值分开(命令行参数有多种设置方式,可不加等号)

    1
    2
    3
    4
    5
    6
    7
      # 示例文件 /tmp/myflags:
    --nobig_menus
    --languages=english,french

    # 两种等价方式
    ./myapp --foo --nobig_menus --languages=english,french --bar # --foo 和 --bar 泛指其他参数
    ./myapp --foo --flagfile=/tmp/myflags --bar
    • 注意:flagfiles方式,一些错误会默认禁止。特别的,未识别的标记名异常默认忽略(这在命令行设置方式下,需要显式使用--undefok 指定),以及未指定值的异常(比如在flagfile中只定义 --languages ,而不指定参数)

    • 通常的 flagfile 比示例要复杂:一系列文件名,每行一个,每个后面紧接着一系列标记,每行一个,根据需要重复多次。flagfile中的文件名可用通配符,比如 *?,仅当当前程序的名称和flagfile文件中filenames中的一个匹配时,才会处理其挨着的标记

    • # 开头的行会被忽略

    • 空白行及前置空白符会被忽略

    • 可在 flagfile 中使用 --flagfile 指定其他 flagfile

    • 标记总是以期望的方式处理,首先从检查命令行参数开始,如果遇到 flagfile ,则处理其内容,然后继续处理后续标记

1
2
3
4
5
6
7
8
9
10
11
# 示例文件 gflags_test.cmd
gflags-test-foo
--port=10000
--service_name=gflags_test_foo
gflags-test-bar
--port=20000
--service_name=gflags_test_bar

# 如果程序名称是 gflags-test-foo,则 --port=10000 --service_name=gflags_test_foo
# 如果程序名称是 gflags-test-bar,则 --port=20000 --service_name=gflags_test_bar
# 实例程序参考这里 https://github.com/sunzhenkai/cooking/blob/master/c%2B%2B/universal/scripts/gflags_test.sh

其他

移除帮助信息

可减少编译源文件时的帮助信息,及二进制文件大小,或一些安全相关的隐患。

1
2
#define STRIP_FLAG_HELP 1    // this must go before the #include!
#include <gflags/gflags.h>

问题

NO 1

描述

1
ERROR: something wrong with flag 'flagfile' in file '/home/wii/Git/cooking/c++/universal/third/build/gflags-2.2.2/src/gflags.cc'.  One possibility: file '/home/wii/Git/cooking/c++/universal/third/build/gflags-2.2.2/src/gflags.cc' is being linked both statically and dynamically into this executable.

解决

在编译gflags时,使用 cmake 参数 -DBUILD_SHARED_LIBS=1 指定只编译共享库。

参考

2021 todo list

事项

  • 早睡早起,每天12点前睡觉,保持七个小时睡眠
  • 每天2L水
  • 徒步(10公里+) / 骑行(20公里+)8次
  • [ ] 体重控制在140以下
  • 读25本书,8本技术书
  • 上线博客站点,文章 50+
  • 掌握特征工程及模型训练
  • 精读一种训练框架源码(tensorflow、xdl、paddlepaddle)
  • 熟读SpringMVC源码
  • 熟读一种微服务框架(dubbo、Spring Cloud)
  • 熟读一种DB源码(RocksDB、Redis、HBase)

读书

文学

  • [ ] 百年孤独
  • [x] 人生海海

经济学

  • [ ] 贫穷的本质
  • [ ] 经济学原理
  • [ ] 结构性改革

心理学

  • [x] 被讨厌的勇气
  • [ ] 反脆弱

传记

  • [x] 只有偏执狂才能生存
  • [ ] 忏悔录

互联网

  • [ ] 浪潮之巅

技术

  • [x] 设计模式(Head First)
  • [ ] 分布式服务架构原理、设计与实战

效率

  • [ ] 高效能人士的7个习惯

成长

  • [ ] 想哲学家一样生活
  • [ ] 沉思录

文章

算法

效率

cmake/makefile flags

规约

编译源文件

1
$(CC) $(CPPFLAGS) $(CFLAGS) example.c -c -o example.o # -c: 编译,不执行链接操作

链接

1
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) example.c -o example # -o: 指定输出文件名字

命令

命令 说明
CC C 编译器
CXX C++ 编译器
CPP C / C++ 预编译器,通常是 “$(CC) -E”

示例

1
2
export CC=/usr/local/bin/gcc-7
export CXX=/usr/local/bin/g++-7

变量

变量 含义 示例
CFLAGS C 编译器选项
CXXFLAGS C++ 编译器选项
CPPFLAGS C/C++ 预处理器的命令行参数
LDFLAGS 链接参数
LD_LIBRARY_PATH 运行时动态链接库查找路径
LIBRARY_PATH 编译时链接库查找路径 /usr/lib
C_INCLUDE_PATH 头文件查找路径
CPLUS_INCLUDE_PATH C++ 头文件查找路径
OBJC_INCLUDE_PATH ObjectiveC 头文件查找路径
CPATH C/C++/ObjectiveC 头文件默认查找路径,多个路径使用 : 分隔,比如 .:/root/include
DYLD_LIBRARY_PATH Mac OS 动态链接库查找路径

参数

参数 说明 示例
c 编译 -c
o 输出文件名称 -o
g 添加调试信息 -g
l(小写L) 链接标准库 -lz
L 指定库搜索路径 -L/user/local/lib
I(大写i) 指定头文件搜索路径 -I/user/local/include
static 在支持动态链接的系统中,该参数覆盖-pie,并阻止链接共享库 -static
shared 生成可被链接的共享对象 -shared
PIC / pic 使用位置无关代码创建对象文件,创建共享库时需指定 -fpic / -fPIC

说明

  • -llibrary / -l library 链接时,搜索指定库,优先使用共享库,除非指定 -static 参数

cmake 使用

示例

CMakeLists.txt

1
2
3
$ mkdir cmake-project
$ cd cmake-project
$ touch CMakeLists.txt

Code

1
$ mkdir src && vim src/main.cpp

Config

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.10)

# set the project name
project(Tutorial)

# add the executable
add_executable(Tutorial src/main.cpp)

Build

1
2
3
$ mkdir build && cd build
$ cmake ..
$ make

Run

1
2
$ ./Tutorial
Hello, CMake.

语法示例

1
2
3
cmake_minimum_required(VERSION 2.8)
project("pybindcpp")
add_executable(HELLO src/pybindcpp.cpp)

完整示例

1
2
3
4
cmake_minimum_required(VERSION 2.8)
project("pybindcpp")

add_executable(HELLO src/pybindcpp.cpp)

编译

1
2
3
$ mkdir build && cd build
$ cmake ..
$ make

macro

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
macro(AddLibrary MODULE)
set(options NONE)
set(oneValueArgs PREFIX DEP)
set(multiValueArgs SUBMODULES)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
message(STATUS "AddLibrary MODULE=${MODULE} PREFIX=${ARG_PREFIX} DEP=${ARG_DEP} SUBMODULES=${ARG_SUBMODULES}")


if ("${ARG_PREFIX}" STREQUAL "")
message(FATAL_ERROR "PREFIX should not be empty")
endif ()
foreach (I IN LISTS ARG_SUBMODULES)
set(TGT ${MODULE}::${I})
add_library(${TGT} STATIC IMPORTED GLOBAL)
set_target_properties(${TGT} PROPERTIES
IMPORTED_LOCATION "${ARG_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${I}${CMAKE_STATIC_LIBRARY_SUFFIX}"
INCLUDE_DIRECTORIES ${ARG_PREFIX}/include)
add_dependencies(${TGT} ${ARG_DEP})
endforeach ()
endmacro(AddLibrary)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
macro(AddLibraryV2 MODULE)
set(options NONE)
set(oneValueArgs PREFIX DEP)
set(multiValueArgs SUBMODULES)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
message(STATUS "AddLibrary MODULE=${MODULE} PREFIX=${ARG_PREFIX} DEP=${ARG_DEP} SUBMODULES=${ARG_SUBMODULES}")


if ("${ARG_PREFIX}" STREQUAL "")
message(FATAL_ERROR "PREFIX should not be empty")
endif ()
foreach (I IN LISTS ARG_SUBMODULES)
find_path(TGT_LIB_${I} NAMES "${I}" "lib${I}" HINTS ${ARG_PREFIX} PATH_SUFFIXES lib lib64)
find_path(TGT_INCLUDE_${I} NAMES "${MODULE}" HINTS ${ARG_PREFIX} PATH_SUFFIXES include)
set(TGT ${MODULE}::${I})
message(STATUS "AddLibrary TARGET=${TGT} TARGET_LIB_DIR=${TGT_LIB_${I}} TARGET_INCLUDE_DIR=${TGT_INCLUDE_${I}}")
add_library(${TGT} STATIC IMPORTED GLOBAL)
set_target_properties(${TGT} PROPERTIES
IMPORTED_LOCATION "${TGT_LIB_${I}}/${CMAKE_STATIC_LIBRARY_PREFIX}${I}${CMAKE_STATIC_LIBRARY_SUFFIX}"
INCLUDE_DIRECTORIES ${TGT_INCLUDE_${I}})
add_dependencies(${TGT} ${ARG_DEP})
include_directories(${TGT_INCLUDE_${I}})
endforeach ()
endmacro(AddLibraryV2)

cmake 使用

简介

CMake是一个比make更高级的编译配置工具,它可以根据不同平台、不同的编译器,生成相应的Makefile或者vcproj项目。通过编写CMakeLists.txt,可以控制生成的Makefile,从而控制编译过程。CMake自动生成的Makefile不仅可以通过make命令构建项目生成目标文件,还支持安装(make install)、测试安装的程序是否能正确执行(make test,或者ctest)、生成当前平台的安装包(make package)、生成源码包(make package_source)、产生Dashboard显示数据并上传等高级功能,只要在CMakeLists.txt中简单配置,就可以完成很多复杂的功能,包括写测试用例。如果有嵌套目录,子目录下可以有自己的CMakeLists.txt。

步骤

  • 编写CMakeLists.txt
  • 执行命令cmake PATHccmake PATH 生成Makefile
  • 使用make命令编译,make -j4 指定编译并行度
  • 使用 make install 安装

编译

指定安装路径

参考 这里

1
2
3
4
5
6
# 3.15 +
$ cmake --install /path/to/build --prefix /path/to/install [--config <CONFIG>]

# < 3.15
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=/path/to/install -P cmake_install.cmake

指定编译类型

1
2
3
4
5
# 1
$ cmake -DCMAKE_INSTALL_PREFIX:PATH=/path/to/install -DCMAKE_BUILD_TYPE=Release /path/to/src

# 2
$ cmake --config Release --build . --target install

语法

CMakeLists.txt 的语法比较简单,由命令、注释和空格组成。# 后面的是注释。

commond (args ...)

  • commond为命令名,大小写不敏感
  • args为参数
    • 如果包含空格,使用双引号

变量引用

变量引用用 ${VAR} 语法

命令

set

set命令将多变量放在一起。

1
set (Foo a b c)
  • commond(${Foo}) 等价于 command(a b c)
  • commond("${Foo}") 等价于 command("a b c")

cmake_minimum_required

指定运行此配置文件所需的CMake的最低版本。

cmake_minimum_required (VERSION 2.8)

project

指定项目信息。

1
project (Demo)

add_executable

指定生成目标。

1
add_executable(Demo demo.cpp)

aux_source_directory

查找指定目录下的所有源文件,将结果存进指定变量名。

1
aux_source_directory(<dir> <variable>)
1
2
3
4
cmake_minimum_required (VERSION 2.8)
project (Demo)
aux_source_directory (. DIR_SRCS)
add_executable(Demo ${DIR_SRCS})

add_subdirectory

指明项目包含一个子目录,这样该目录下的CMakeLists.txt文件和源文件也会被处理。

1
add_subdirectory(math)

指明可执行文件需要链接的库。

1
target_link_libraries(Demo MathFunctions)

add_library

将指定的源文件生成链接文件,然后添加到工程中去

1
2
aux_source_directory(. DIR_LIB_SRCS)
add_library(MathFunctions ${DIR_LIB_SRCS})

指定连接器查找库的文件夹。此命令的相对路径被解释为相对于当前源目录。

格式

1
link_directories(directory1 directory2 ...)

示例

1
link_directories(${PROJECT_BINARY_DIR}/third_party/googletest/)

include_directories

将给定的目录添加到编译器用来搜索头文件的目录中。相对路径被解释为相对于当前源目录。

格式

1
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

示例

1
2
include_directories(${PROJECT_SOURCE_DIR}/third_party/glog/)
include_directories(${PROJECT_SOURCE_DIR}/third_party/hdfs/)

find_package_handle_standard_args

用于 find_package 的实现。

变量

Cache

1
2
3
4
5
6
7
# CACHE, 不覆盖已有值. 
## 用途
### 1. 在命令行指定变量, 且不被覆盖
set(MY_CACHE_VARIABLE "VALUE" CACHE STRING "Description")

# FORCE, 强制设置
set(MY_CACHE_VARIABLE "VALUE" CACHE STRING "" FORCE)

常用变量

  • PROJECT_BINARY_DIR
    • Full path to build directory for project.
  • PROJECT_SOURCE_DIR
    • Top level source directory for the current project.
  • CMAKE_CURRENT_SOURCE_DIR
    • This the full path to the source directory that is currently being processed by cmake.

Scope

1
2
3
add_subdirectory : 建立新的 scope
include : 不会建立新的 scope
function : 建立新的 scope

示例

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ tree
.
├── CMakeLists.txt
└── t.cpp

0 directories, 2 files
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ cat t.cpp
#include <iostream>

using namespace std;

int main() {
cout<<"Hello"<<endl;
return 0;
}
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ cat CMakeLists.txt
project("test")
add_executable(Hello t.cpp)
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ cmake .
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.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/bovenson/Git/notes/C++/Code/CMake/test
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ tree
.
├── CMakeCache.txt
├── CMakeFiles
│ ├── 3.5.1
│ │ ├── CMakeCCompiler.cmake
│ │ ├── CMakeCXXCompiler.cmake
│ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ ├── CMakeSystem.cmake
│ │ ├── CompilerIdC
│ │ │ ├── a.out
│ │ │ └── CMakeCCompilerId.c
│ │ └── CompilerIdCXX
│ │ ├── a.out
│ │ └── CMakeCXXCompilerId.cpp
│ ├── cmake.check_cache
│ ├── CMakeDirectoryInformation.cmake
│ ├── CMakeOutput.log
│ ├── CMakeTmp
│ ├── feature_tests.bin
│ ├── feature_tests.c
│ ├── feature_tests.cxx
│ ├── Hello.dir
│ │ ├── build.make
│ │ ├── cmake_clean.cmake
│ │ ├── DependInfo.cmake
│ │ ├── depend.make
│ │ ├── flags.make
│ │ ├── link.txt
│ │ └── progress.make
│ ├── Makefile2
│ ├── Makefile.cmake
│ ├── progress.marks
│ └── TargetDirectories.txt
├── cmake_install.cmake
├── CMakeLists.txt
├── Makefile
└── t.cpp

6 directories, 31 files
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ make
Scanning dependencies of target Hello
[ 50%] Building CXX object CMakeFiles/Hello.dir/t.cpp.o
[100%] Linking CXX executable Hello
[100%] Built target Hello
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ tree
.
├── CMakeCache.txt
├── CMakeFiles
│ ├── 3.5.1
│ │ ├── CMakeCCompiler.cmake
│ │ ├── CMakeCXXCompiler.cmake
│ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ ├── CMakeDetermineCompilerABI_CXX.bin
│ │ ├── CMakeSystem.cmake
│ │ ├── CompilerIdC
│ │ │ ├── a.out
│ │ │ └── CMakeCCompilerId.c
│ │ └── CompilerIdCXX
│ │ ├── a.out
│ │ └── CMakeCXXCompilerId.cpp
│ ├── cmake.check_cache
│ ├── CMakeDirectoryInformation.cmake
│ ├── CMakeOutput.log
│ ├── CMakeTmp
│ ├── feature_tests.bin
│ ├── feature_tests.c
│ ├── feature_tests.cxx
│ ├── Hello.dir
│ │ ├── build.make
│ │ ├── cmake_clean.cmake
│ │ ├── CXX.includecache
│ │ ├── DependInfo.cmake
│ │ ├── depend.internal
│ │ ├── depend.make
│ │ ├── flags.make
│ │ ├── link.txt
│ │ ├── progress.make
│ │ └── t.cpp.o
│ ├── Makefile2
│ ├── Makefile.cmake
│ ├── progress.marks
│ └── TargetDirectories.txt
├── cmake_install.cmake
├── CMakeLists.txt
├── Hello
├── Makefile
└── t.cpp

6 directories, 35 files
bovenson@HP:~/Git/notes/C++/Code/CMake/test$ ./Hello
Hello

参考

cmake 使用

Command Line

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
$ Generate a Project Buildsystem
# cmake [<options>] <path-to-source>
# cmake [<options>] <path-to-existing-build>
# cmake [<options>] -S <path-to-source> -B <path-to-build>

$ Build a Project
# cmake --build <dir> [<options>] [-- <build-tool-options>]

$ Install a Project
# cmake --install <dir> [<options>]

$ Open a Project
# cmake --open <dir>

$ Run a Script
# cmake [{-D <var>=<value>}...] -P <cmake-script-file>

$ Run a Command-Line Tool
# cmake -E <command> [<options>]

$ Run the Find-Package Tool
# cmake --find-package [<options>]

$ View Help
# cmake --help[-<topic>]

内置变量

1
2
3
4
PROJECT_SOURCE_DIR           项目目录
CMAKE_CURRENT_LIST_DIR 当前 cmake 文件所在目录
CMAKE_STATIC_LIBRARY_PREFIX 静态库前缀, 例如 lib
CMAKE_STATIC_LIBRARY_SUFFIX 静态库后缀, 例如 .a

CMakeLists

设置cmake最小版本

1
cmake_minimum_required(VERSION 2.8)

设置项目名称

1
project("...")

判断OS

1
2
3
4
5
6
7
if (APPLE)
# do something
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
elseif (UNIX)
# do something
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()

生成共享库

1
add_library(name SHARED src)

使用静态库

1
2
3
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")    # 查找库文件后缀
SET(BUILD_SHARED_LIBS OFF) # 关闭使用共享库
SET(CMAKE_EXE_LINKER_FLAGS "-static") # 连接时使用静态库

生成可执行文件

1
add_executable(MAIN src/main.cpp)

包含cmake文件

1
include(path/to/cmake)

打印消息

1
MESSAGE("msg...")

指定compiler

1
2
set(CMAKE_C_COMPILER "gcc-5")
set(CMAKE_CXX_COMPILER "g++-5")

编译类型

1
set(CMAKE_BUILD_TYPE=Release)	# or Debug

指定FLAGS

1
2
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

遍历

1
2
3
4
set(L 1 2 3)
foreach(ITEM IN LISTS L)
messag(STATUS "item: ${ITEM}")
endforeach()

循环

1
2
3
4
set (L A B C)
foreach (V IN LISTS L)
... ${V}
endforeach()

打印 Target 属性

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
FUNCTION(PrintTargetProperties _tgt)
IF (NOT CMAKE_PROPERTY_LIST)
EXECUTE_PROCESS(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)

# Convert command output into a CMake list
STRING(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
STRING(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
LIST(REMOVE_DUPLICATES CMAKE_PROPERTY_LIST)
ENDIF ()

IF (NOT TARGET ${_tgt})
MESSAGE(STATUS "[TargetProperties] There is no target named '${_tgt}'")
RETURN()
ENDIF ()

FOREACH (property ${CMAKE_PROPERTY_LIST})
STRING(REPLACE "<CONFIG>" "${CMAKE_BUILD_TYPE}" property ${property})

# Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i
IF (property STREQUAL "LOCATION" OR property MATCHES "^LOCATION_" OR property MATCHES "_LOCATION$")
CONTINUE()
ENDIF ()

GET_PROPERTY(_was_set TARGET ${_tgt} PROPERTY ${property} SET)
IF (_was_set)
GET_TARGET_PROPERTY(value ${_tgt} ${property})
MESSAGE("[TargetProperties] ${_tgt} ${property} = ${value}")
ENDIF ()
ENDFOREACH ()
ENDFUNCTION(PrintTargetProperties)

使用

1
PrintTargetProperties(spdlog::spdlog)

添加定义(add_definitions)

添加字符串

1
2
3
4
5
6
7
8
9
execute_process(
COMMAND sh -c "git rev-parse --short HEAD"
OUTPUT_VARIABLE TAG_REVERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if (TAG_REVERSION STREQUAL "")
set(TAG_REVERSION "UNKNOWN")
endif ()
add_definitions(-DTAG_REVERSION="${TAG_REVERSION}") # 添加字符串

添加定义

1
add_definitions(-DDEBUG_MODE) # 添加字符串

使用

1
2
3
4
5
#ifdef DEBUG_MODE
...
#else
...
#endif

添加文件

1
file(GLOB_RECURSE SRCS src/**.cpp)  # 递归添加 src 下所有的 cpp 文件

变量

判断变量是否定义

1
2
3
if (DEFINED VAR_NAME) # NOT DEFINED VAR_NAME
...
endif()

转换字符串为大写

1
string(TOUPPER ${ORIGIN_VAR} DEST_VAR)

判断变量为空

1
2
3
if (${V} STREQUAL "")
...
endif()

变量默认值

1
2
3
4
5
6
# option, 只对 BOOL 类型, 默认 OFF
option(BUILD_THIRD_PARTY "build third party library" ON)

# cache, STRING 类型等
set(BUILD_THIRD_PARTY ON CACHE BOOL "build third party library")
set(DEPS_DIR "/tmp/cpp-external-lib" CACHE STRING "library install prefix" )

编译和安装

1
2
3
4
5
6
7
8
9
10
# build director
$ mkdir build
$ cd build
# cmake configure
$ cmake ..
$ cmake -DCMAKE_INSTALL_PREFIX=$PWD ..
# build
$ cmake --build .
# install
$ cmake --install . --config Release # debug...

PkgConfig

{library}.pc.in

1
2
3
4
5
6
7
8
9
10
11
12
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
includedir=@PKG_CONFIG_INCLUDEDIR@
libdir=@PKG_CONFIG_LIBDIR@

Name: lib@PROJECT_NAME@
Description: cpp common
URL: https://github.com/sunzhenkai/cpp-common
Version: @CPP_COMMON_VERSION@
CFlags: -I${includedir}
Libs: -L${libdir}
Requires: @PKG_CONFIG_REQUIRES@

cmake config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SET(PKG_CONFIG ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc)

IF (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
SET(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
ELSE ()
SET(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
ENDIF ()
IF (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
SET(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
ELSE ()
SET(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
ENDIF ()
CONFIGURE_FILE("cmake/${PROJECT_NAME}.pc.in" "${PKG_CONFIG}" @ONLY)
INSTALL(FILES "${PKG_CONFIG}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")

修改库搜索路径

1
2
export CMAKE_PREFIX_PATH="$CUSTOME_LIBRARY_PATH"
export CMAKE_LIBRARY_PATH="$CUSTOME_LD_LIBRARY_PATH"

查找&链接库

1
2
3
4
5
# 将库路径写入 CMAKE_PREFIX_PATH
set(CMAKE_PREFIX_PATH ${PATH_TO_LIB} ${CMAKE_PREFIX_PATH})
find_package(<library-name> REQUIRED)
# 使用
target_link_libraries(<library-name> <target-name>)

示例

1
2
find_package(Snappy REQUIRED)
target_link_libraries(brpc Snappy::snappy)

自定义 Find 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
find_path(THRIFT_INCLUDE_DIR
NAMES
thrift/Thrift.h
HINTS
/usr/local
PATH_SUFFIXES
include
)

find_library(THRIFT_LIBRARIES
NAMES
thrift libthrift
HINTS
/usr/local
PATH_SUFFIXES
lib lib64
)

find_program(THRIFT_COMPILER
NAMES
thrift
HINTS
/usr/local
PATH_SUFFIXES
bin bin64
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(THRIFT DEFAULT_MSG THRIFT_LIBRARIES THRIFT_INCLUDE_DIR THRIFT_COMPILER)
# 设置变量为高级,在 GUI 模式下默认不展示
mark_as_advanced(THRIFT_LIBRARIES THRIFT_INCLUDE_DIR THRIFT_COMPILER)

引入库

引入第三方库

引入第三方库的几种方式。第一种,find_path 查找头文件,find_library 查找库文件,分别使用 include_directories(DEP_INCLUDE_DIR)、target_link_libraries(target library) 链接库,这种方式一般用于没有 Find*.cmake 的库。第二种,对于有 Find cmake 的库,可以使用 find_package(Library REQUIRED) 来 import 库,然后使用 target_link_libraries 来链接库。第三种,自定义 Find Cmake 文件,借助 find_package_handle_standard_args 实现。对于有 pkgconfig 的库来说,也可以用 pkg_check_modules 来导入,但是有个问题,pkgconfig 内可能有写死的 prefix,移动之后可能会出现找不到库的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# snappy
find_package(Snappy REQUIRED)
target_link_libraries(brpc-static Snappy::snappy)

# thrift
find_path(THRIFT_INCLUDE_DIR NAMES thrift/Thrift.h PATH_SUFFIXES include)
find_library(thrift thrift REQUIRED CONFIG)
include_directories(${THRIFT_INCLUDE_DIR})
target_link_libraries(brpc-static thrift)

# pkg_check_modules
include(FindPkgConfig)
pkg_check_modules(Curl libcurl REQUIRED)
# Curl_INCLUDE_DIR、Curl_LIBRARIES、Curl_FOUND 会被设置

示例

1
2
3
include(FindPkgConfig)
pkg_check_modules(brpc REQUIRED IMPORTED_TARGET brpc)
target_link_libraries(target PkgConfig::brpc)

使用 PkgConfig 文件添加库

简版

文档

1
2
3
4
5
6
7
8
# search
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)

# link
target_link_libraries(testapp ${SDL2_LIBRARIES})
target_include_directories(testapp PUBLIC ${SDL2_INCLUDE_DIRS})
target_compile_options(testapp PUBLIC ${SDL2_CFLAGS_OTHER})

自己添加 target

find_package

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
# 完整签名
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[CONFIG|NO_MODULE]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_INSTALL_PREFIX]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])

CONFIG 命令

1
find_package(PackageName CONFIG)

CONFIG 命令会尝试搜索包提供的 <PackageName>Config.cmake<lowercasePackageName>-config.cmake 文件,并把包含该文件的文件夹路径赋值给 <PackageName>_DIR<PackageName>_CONFIG 保存配置文件的完整路径。

默认搜索路径

指定搜索路径

1
2
find_package (<package> PATHS paths... NO_DEFAULT_PATH)
# NO_DEFAULT_PATH: 不使用默认路径

设置变量

1
2
# 不管是否找到都会设置
{PackageName}_FOUND

库管理

ExternalProject_Add

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
include(ExternalProject)

set(target spdlog)
set(CMAKE_ARGS
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_INSTALL_PREFIX=${DEPS_PREFIX}
-DCMAKE_INSTALL_LIBDIR=lib
-DBUILD_STATIC_LIB=ON
-DBUILD_SHARED_LIB=OFF)
ExternalProject_Add(
${target}_build
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.9.2
CMAKE_ARGS ${CMAKE_ARGS}
)

# 指定 libary 安装文件夹,统一在 lib/lib64
-DCMAKE_INSTALL_LIBDIR=lib

# 参数
ExternalProject_Add(
<target-name>
GIT_REPOSITORY <git-repo-address>
GIT_TAG <git-tag>
PREFIX <prefix-path> # 创建 build、src 等目录所在的位置, 不是安装的路径
INSTALL_DIR <install_dir> # 不是安装的位置,作为属性,可用 ExternalProject_Get_Property 获取,在 CONFIGURE_COMMAND 等中指定 prefix
CMAKE_ARGS ${CMAKE_ARGS}
)

传入 CMAKE_C_FLAGS / CMAKE_CXX_FLAGS

1
2
3
4
5
6
7
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lstdc++ -ldl")
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS})

# 注意: 下面方式均不可
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS=-lstdc++ -ldl)
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS="-lstdc++ -ldl")
ExternalProject_Add(... CMAKE_ARGS -DCMAKE_C_FLAGS='-lstdc++ -ldl')

AddLibrary

1
2
3
4
5
6
7
8
9
add_library(${TGT} STATIC IMPORTED GLOBAL)
set_target_properties(${TGT} PROPERTIES
IMPORTED_LOCATION "${TGT_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}${TGT}${CMAKE_STATIC_LIBRARY_SUFFIX}"
INCLUDE_DIRECTORIES ${TGT_PREFIX}/include
INTERFACE_INCLUDE_DIRECTORIES ${TGT_PREFIX}/include
)

# INTERFACE_INCLUDE_DIRECTORIES
set_target_propterties 添加 INTERFACE_INCLUDE_DIRECTORIES, 在 target_link_libraries 时,不需要再 include 库的头文件

FetchContent_Declare

FetchContent_Declare 通常和 FetchContent_MakeAvailable 一块使用。

FetchContent_MakeAvailable 是 cmake 3.14 引入的。

FetchContent_Declare 的参数参考 ExternalProject_Add,和 ExternalProject_Add 相比,屏蔽了下面的命令。

  • CONFIGURE_COMMAND
  • BUILD_COMMAND
  • INSTALL_COMMAND
  • TEST_COMMAND

URL 指定本地文件

1
2
3
4
FetchContent_Declare(
boost
URL file:///tmp/boost-submodule-boost-1.80.0-1.tar.gz
)

指定 CMakeLists.txt 路径

如果 CMakeLists.txt 文件不在仓库根目录下,可以用 SOURCE_SUBDIR 来指定子路径。

1
2
3
4
5
6
7
include(FetchContent)
FetchContent_Declare(
protobuf
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
GIT_TAG ae50d9b9902526efd6c7a1907d09739f959c6297 # v3.15.0
SOURCE_SUBDIR cmake
)

Configure 阶段让 Target 可用

方法 (function)

1
2
function(FNAME)
endfunction(FNAME)

作用域

方法有独立的作用域,可以访问父级作用域内的变量。在函数内定义的变量,对父级作用域不可访问。如果需要修改父级作用域变量,需要使用 PARENT_SCOPE。

1
SET(VAR vALUEe PARENT_SCOPE)

参数

参数列表指定

1
2
3
4
5
function(ARG version url flag)
message(STATUS "version: ${version}, url: ${url}, flag: ${flag}")
endfunction(ARG)

ARG(1.0.0 www.so.com true)

非参数列表

首先了解在函数内定义的默认变量。

  • ARGC,参数数量
  • ARGN,参数,去掉声明的参数的参数列表
  • ARGV,参数,全部参数
  • ARG0,ARG1 …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function(ARG4)
# 定义解析变量列表
set(options OPTIONAL FAST)
set(oneValueArgs NAME URL)
set(multiValueArgs KEY)
cmake_parse_arguments(PREFIX "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
message(STATUS "FAST=${PREFIX_FAST} NAME=${PREFIX_NAME} URL=${PREFIX_URL} KET=${PREFIX_KEY}")
endfunction(ARG4)

ARG4(
FAST
NAME beijing
URL www.so.com
KEY weight price
)
# FAST=TRUE NAME=beijing URL=www.so.com KET=weight;price

ARG4(
URL www.so.com
KEY band price
)
# FAST=FALSE NAME= URL=www.so.com KET=band;price

宏 (macro)

1
2
macro(MName)
endmacro(MName)

Macro 和 function 比较相似,区别如下。

  • macro 和调用域共享变量的作用域,function 则有独立的作用域

参考

编译

使用 preset

1
cmake --preset=default  # 读取 CMakePresets.json 中的 default 配置项

CMakePresets.json 示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": 2,
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/vcpkg-build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}

指定 Target

1
2
3
4
# pwd: {project}/build
cmake ..
cmake --build . --target {target}
cmake --build . --target mongoc_mongoc

优化

ccache

1
2
3
4
5
# 安装
sudo yum install ccache

# 配置
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache

安装

指定安装目录。

1
2
3
4
# 系统路径
cmake --install {build-dir} --prefix "/usr"
# 示例
cmake --install build --prefix "$PWD"

安装头文件

1
2
# 使用 install
install(DIRECTORY include/ DESTINATION include) # FILES_MATCHING PATTERN "*.h"

brpc c++

描述

brpc c++ 是百度基于c++编写的RPC框架,文档参考这里

编译

编译文档参考这里。编译brpc之前,首先需要准备依赖,brpc一下如下库。

  • gflags:定义全局变量
  • protobuf:序列化消息及服务接口
  • leveldbrpcz 需要,记录RPC踪迹