Modern CMake

  • 个人认为也可以包含在 6.NULL 系列的 CMake 教程

参考链接

CMake and Make

  • CMake 最终实现的是帮助编译成 Makefile
  • 本文参考了上述链接,重点是 B站 IPADS 的新人培训教程,对 Makefile 和 CMake 进行了讲解,然后个人再做一些补充。

示例

step0. Hello world

  • 了解 Makefile 最基本的格式
    • CXX 是 Make 的内置变量,还有一些 其他的内置变量
      • 普通变量:
        • 简单赋值 ( := )
        • 递归赋值 ( = )
        • 条件赋值 ( ?= )
        • 追加赋值 ( += )
      • 自动化变量
    • targets:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;
    • prerequisites:是我们的依赖文件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;
    • command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。
    • 注意:我们的目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用Tab键。

name: dependencies
	commands

# or
targets : prerequisites
    commands

# or
targets : prerequisites; command
    command
  • 源代码程序 main.cpp
#include <iostream>

int main(int argc, const char *argv[])
{
    std::cout << "Hello world!" << std::endl;
    return 0;
}
  • Makefile
hello: main.cpp
	$(CXX) -o hello main.cpp
	echo "OK"
  • 执行
    • 这里 make 后的 hello 就对应了前面 Makefile 里定义的 hello
$ make hello
$ ./hello

step1. 优化前面的 Makefile

  • 虽然前面的 Makefile 足够简单,但是还不够优雅,特别是需要添加新的源代码文件时候可能需要修改的地方过多,所以将其优化。
  • Makefile
    • 定义了全局变量 CC, CXX
    • 定义了伪目标 all 和 clean
    • 定义了中间文件变量(编译是需要首先将源文件编译成 .o 的文件,再编译成可执行文件)此处定义的是汇编过后的机器码作为中间文件变量
      • 预处理:C 编译器对各种预处理命令进行处理,包括头文件包含、宏定义的扩展、条件编译的选择等;
      • 编译,将预处理得到的源代码文件,进行“翻译转换”,产生出机器语言的目标程序,得到机器语言的汇编文件;
      • 汇编,将汇编代码翻译成了机器码,但是还不可以运行;
      • 链接,处理可重定位文件,把各种符号引用和符号定义转换成为可执行文件中的合适信息,通常是虚拟地址。
        20220209101007
    • 执行 make all 时对应跳转到执行 make hello,再跳转到执行 make main.o,相应地将 main.cpp 编译成了 main.o,然后 hello 的 command 将对应的目标文件链接起来构成对应的可执行文件 hello
    • make clean 删除对应的编译的中间文件和可执行文件
  • 注意
    • 全局变量定义的部分可通过 make CXX=g++ 形式覆盖
    • 链接到目标文件过程中的 $@ 是自动变量,表示 target 名
#
# := 用于给变量赋值,除此之外还有 =、?=、+= 等不同的赋值方式。
#
# 一般全大写变量用来表示允许调用 make 的时候传入的变量,
# 全小写变量表示仅内部使用的变量。
#
# 这里 CC 和 CXX 指定了要使用的 C 和 C++ 编译器。
#
CC := clang
CXX := clang++

#
# Makefile 中的核心概念是 target(目标),定义 target 的基本
# 格式是(注意每一行 command 是必须用 tab 缩进的):
#
#   name: dependencies
#   	commands
#
# 要构建某个 target 时,使用如下命令:
#
#   make target_name
#
# 下面 all 是一个 target,它依赖另一个 target:hello,
# 意味着要构建 all,首先需要构建 hello。而 all 的 commands
# 部分为空,表示构建 all 不需要额外命令。
#
# .PHONY 表示 all 不是一个真实会生成的文件,而是一个“伪目标”。
#
.PHONY: all
all: hello

#
# 由于后面需要多次使用 main.o 等目标文件列表,这里赋值给变量
# objects。
#
objects := main.o

#
# hello 是我们最终要生成的可执行文件名,它依赖 objects 中的
# 所有目标文件。
#
# 它的 commands 部分使用 CXX 指定的编译器将所有目标文件链接
# 成 hello 可执行文件。
#
hello: $(objects)
	$(CXX) -o $@ $(objects)

# main.o 目标文件依赖 main.cpp 源文件。
main.o: main.cpp
	$(CXX) -c main.cpp

#
# clean 用于清除构建生成的临时文件、目标文件和可执行文件。
# 和 all 类似,它是一个“伪目标”。
#
.PHONY: clean
clean:
	rm -f hello $(objects)

step2. 引入新的头文件和源码

  • 新添加了一个头文件和源代码文件
  • answer.hpp
#pragma once

namespace answer {
    int find_the_ultimate_answer();
} // namespace answer
  • answer.cpp
#include "answer.hpp"

namespace answer {
    int find_the_ultimate_answer() {
        return 42;
    }
} // namespace answer
  • main.cpp
#include <iostream>

#include "answer.hpp"

int main(int argc, const char *argv[]) {
    int expected_answer = answer::find_the_ultimate_answer();
    for (;;) {
        std::cout << "What is the ultimate answer?" << std::endl;
        int answer;
        std::cin >> answer;
        if (answer == expected_answer) {
            std::cout << "Correct!" << std::endl;
            break;
        }
    }
    return 0;
}

  • 此时的 Makefile
    • 因为新引入了 answer 源码,所以多了一个目标文件 answer.o
    • 相应的目标文件 make answer.o 需要添加对应的步骤,因为有对应的规范,所以 Make 其实可以通过对应的目标名推断出同名的 .cpp 文件,只需要指定目标文件所依赖的头文件,使头文件变动时可以重新编译对应目标文件
CC := clang
CXX := clang++

.PHONY: all
all: answer

# 在这里添加了 answer.o 目标文件。
objects := main.o answer.o

answer: $(objects)
	$(CXX) -o $@ $(objects)

#
# Make 可以自动推断 .o 目标文件需要依赖同名的 .cpp 文件,
# 所以其实不需要在依赖中指定 main.cpp 和 answer.cpp,
# 也不需要写编译 commands,它知道要用 CXX 变量制定的命令
# 作为 C++ 编译器。
#
# 这里只需要指定目标文件所依赖的头文件,使头文件变动时可以
# 重新编译对应目标文件。
#
main.o: answer.hpp
answer.o: answer.hpp

.PHONY: clean
clean:
	rm -f answer $(objects)

Makefile 其他规则

通配符
  • Makefile 是可以使用 shell 命令的,所以 shell 支持的通配符在 Makefile 中也是同样适用的。不能通过引用变量的方式来使用
    • * 匹配0个或者是任意个字符
    • ? 匹配任意一个字符
    • [] 我们可以指定匹配的字符放在 "[]" 中
    • "%" 和通配符 "*" 相类似的字符,也是匹配任意个字符
.PHONY:clean
clean:
    rm -rf *.o test

test:*.c
    gcc -o $@ $^

# 不能使用 OBJ=*.c
# 要使用一个函数 "wildcard",这个函数在我们引用变量的时候,会帮我们展开
OBJ=$(wildcard *.c)
test:$(OBJ)
    gcc -o $@ $^

# "%.o" 把我们需要的所有的 ".o" 文件组合成为一个列表,
# 从列表中挨个取出的每一个文件,"%" 表示取出来文件的文件名(不包含后缀)
# 然后找到文件中和 "%"名称相同的 ".c" 文件
# 然后执行下面的命令,直到列表中的文件全部被取出来为止
test:test.o test1.o
    gcc -o $@ $^
%.o:%.c
    gcc -o $@ $^
自动化变量
  • http://c.biancheng.net/view/7094.html
  • 执行 make 的时候,make 会自动识别命令中的自动化变量,并自动实现自动化变量中的值的替换,这个类似于编译C语言文件的时候的预处理的作用。
    • $@:表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a文件为文档文件,也成为静态的库文件),那么它代表这个文档的文件名。在多目模式规则中,它代表的是触发规则被执行的文件名。
    • $%:当目标文件是一个静态库文件时,代表静态库的一个成员名。
    • $<:规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,它代表由隐含规则加入的第一个依赖文件。
    • $?:所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库件,代表的是库文件(.o 文件)。
    • $^:代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它代表的只能是所有的库成员(.o 文件)名。一个文件可重复的出现在目标的依中,变量“$^”只记录它的第一次引用的情况。就是说变量“$^”会去掉重复的赖文件。
    • $+:类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链时库的交叉引用场合。
    • $*:在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的分(当文件名中存在目录时,“茎”也包含目录部分)。
#  "$@" 代表的是目标文件test
#  “$^” 代表的是依赖的文件
test:test.o test1.o test2.o
         gcc -o $@ $^
#  “$<” 代表的是依赖文件中的第一个
test.o:test.c test.h
         gcc -o $@ $<
test1.o:test1.c test1.h
         gcc -o $@ $<
test2.o:test2.c test2.h
         gcc -o $@ $<

# 库文件的制作依赖于这三个文件。当修改了其中的某个依赖文件,
# 在命令行执行 make 命令,库文件 "lib" 就会自动更新。
# "$?" 表示修改的文件
lib:test.o test1.o test2.o
    ar r $?

其他规则

  • http://c.biancheng.net/view/7153.html
  • 未来使用时再补充
  • 主要包括:
    20220209214815

step3. okokok

  • Makefile - no
  • CMake - yes
    20220209215103

step4. 使用 CMake 来编译

  • CMake 本身可以支持不同的底层,如 Makefile, Ninja 等,例如要产生Ninja,加上 -G Ninja 即可:cmake -G Ninja
  • CMakeLists.txt 基本格式:
cmake_minimum_required(VERSION 3.9)
project(answer)

add_executable(answer main.cpp answer.cpp)
  • 还是使用上面的源码 answer 和 main
# 指定最小 CMake 版本要求
cmake_minimum_required(VERSION 3.9)
# 设置项目名称
project(answer)

#[[
添加可执行文件 target,类似于原来 Makefile 的:

    answer: main.o answer.o
    main.o: main.cpp answer.hpp
    answer.o: answer.cpp answer.hpp

CMake 会自动找到依赖的头文件,因此不需要特别指定,
当头文件修改的时候,会重新编译依赖它的目标文件。
#]]
# 当前头文件就在当前目录,所以无需特别指定
# 如需指定,可以使用 include_directories
# 格式 add_executable(target srcfiles)
add_executable(answer main.cpp answer.cpp)

#[[
使用如下命令构建本项目:

    cmake -B build      # 生成构建目录
    cmake --build build # 执行构建
    ./build/answer      # 运行 answer 程序
#]]
  • 执行命令
    • cmake -B <buildPath> 使用"-B"参数指定生成目录,这样CMake生成的文件都会集中在这个文件
    • cmake --build <buildPath> 执行类似于 Makefile 中的 make 的效果,同时支持底层为 Ninja 时的 build 操作,类似于一个封装
    • ./build/answer 执行对应的目标程序
cmake -B build      # 生成构建目录,-B 指定生成的构建系统代码放在 build 目录
cmake --build build # 执行构建
./build/answer      # 运行 answer 程序

step5. CMake 拆分库 (库复用)

  • 项目中可以复用的部分可以拆成 library,然后在后面的其他可执行程序中引用该库
# 添加一个 libanswer 的静态库
add_library(libanswer STATIC answer.cpp)

# 依然指定可执行文件
add_executable(answer main.cpp)
# 链接对应的库
target_link_libraries(answer libanswer)
  • CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(answer)

# 添加 libanswer 库目标,STATIC 指定为静态库
add_library(libanswer STATIC answer.cpp)

add_executable(answer main.cpp)

# 为 answer 可执行目标链接 libanswer
target_link_libraries(answer libanswer)

#[[
使用如下命令构建本项目:

    cmake -B build      # 生成构建目录
    cmake --build build # 执行构建
    ./build/answer      # 运行 answer 程序
#]]

step6. 使用子目录来编译

  • 功能独立的模块可以放到单独的子目录:
    • 与之对应的即为也需要对应的 CMakeLists.txt 在对应的子目录下
.
├── answer
│  ├── answer.cpp
│  ├── CMakeLists.txt
│  └── include
│     └── answer
│        └── answer.hpp
├── CMakeLists.txt
└── main.cpp
  • 原来的 CMakeLists.txt
    • add_subdirectory 添加 answer 子目录
    • 然后对应的依赖的的 libanswer 会去子目录中寻找
cmake_minimum_required(VERSION 3.9)
project(answer)

# 添加 answer 子目录
add_subdirectory(answer)

add_executable(answer_app main.cpp)
target_link_libraries(answer_app libanswer)

#[[
使用如下命令构建本项目:

    cmake -B build      # 生成构建目录
    cmake --build build # 执行构建
    ./build/answer_app  # 运行 answer_app 程序
#]]

  • 子目录中的 CMakeLists.txt
    • CMAKE_CURRENT_SOURCE_DIR 是 CMake 内置变量,表示当前 CMakeLists.txt 文件所在目录,此处其实可以省略
    • target_include_directories PUBLIC 参数表示这个包含目录是 libanswer 的公开接口一部分,链接 libanswer 的 target 可以 #include 该目录中的文件。
      • 从而使得 main.cpp 可以直接 #include <answer/answer.hpp>
add_library(libanswer STATIC answer.cpp)

#[[
message 可用于打印调试信息或错误信息,除了 STATUS
外还有 DEBUG WARNING SEND_ERROR FATAL_ERROR 等。
#]]
message(STATUS "Current source dir: ${CMAKE_CURRENT_SOURCE_DIR}")

#[[
给 libanswer 库目标添加 include 目录,PUBLIC 使
这个 include 目录能被外部使用者看到。

当链接 libanswer 库时,这里指定的 include 目录会被
自动添加到使用此库的 target 的 include 路径中。
#]]
target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

step7. 使用其他已有的动态库

  • 此时假设我们的 answer.cpp 需要发起网络请求来获取数据
  • answer.hpp
#pragma once

#include <string>

namespace answer {
    namespace v1 {
        int find_the_ultimate_answer();
    } // namespace v1

    namespace v2 {
        std::string find_the_ultimate_answer();
    } // namespace v2

    using namespace v2;
} // namespace answer
  • answer.cpp
#include "answer/answer.hpp"

#include <curl/curl.h>

namespace answer {
    namespace v1 {
        int find_the_ultimate_answer() {
            return 42;
        }
    } // namespace v1

    namespace v2 {
        std::string find_the_ultimate_answer() {
            // 使用 CURL 调用 WolframAlpha API 获得答案
            // 注:这里的 appid 是演示用的,只有免费的 2000 次/天调用额度,如有实际需要请自行申请
            const auto url = "https://api.wolframalpha.com/v1/result?appid=YAPKJY-8XT9VEYPX9&i=what+is+ultimate+answer";
            const auto curl = curl_easy_init();
            curl_easy_setopt(curl, CURLOPT_URL, url);
            curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
            const auto write_func = [](char *ptr, size_t size, size_t nmemb, void *userdata) {
                auto &result = *static_cast<std::string *>(userdata);
                result.append(ptr, size * nmemb);
                return size * nmemb;
            };
            using WriteFunction = size_t (*)(char *, size_t, size_t, void *);
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, static_cast<WriteFunction>(write_func));
            std::string result = "";
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
            curl_easy_perform(curl); // 暂时不考虑 API 请求失败的情况
            curl_easy_cleanup(curl);
            return result;
        }
    } // namespace v2
} // namespace answer

  • 虽然我们在代码中制定了对应的头文件,并编写了相关代码,但是本质是没有依赖对应的 libcurl 库的。所以我们需要在子目录下对应修改 CMakeLists.txt
    • find_package 寻找已经安装的第三方库的头文件和库文件的位置,参数 REQUIRED 要求必须找到,没找到就报错。
    • target_link_libraries 相应地链接 libcurl,使用了 PRIVATE 表明只有当前子模块 answer 能使用对应的接口。
    • 注意:CURL 和 CURL::libcurl 是约定的名字,其它第三方库的包名和 library 名可在网上查。
#[[
find_package 用于在系统中寻找已经安装的第三方库的头文件和库文件
的位置,并创建一个名为 CURL::libcurl 的库目标,以供链接。
#]]
find_package(CURL REQUIRED)

add_library(libanswer STATIC answer.cpp)

target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

#[[
为 libanswer 库链接 libcurl,这里 PRIVATE 和 PUBLIC 的区别是:
CURL::libcurl 库只会被 libanswer 看到,根级别的 main.cpp 中
无法 include curl 的头文件。
#]]
target_link_libraries(libanswer PRIVATE CURL::libcurl)

step8. 继续解耦

  • 此时的文件目录
.
├── answer
│   ├── answer.cpp
│   ├── CMakeLists.txt
│   └── include
│       └── answer
│           └── answer.hpp
├── CMakeLists.txt
├── curl_wrapper
│   ├── CMakeLists.txt
│   ├── curl_wrapper.cpp
│   └── include
│       └── curl_wrapper
│           └── curl_wrapper.hpp
├── main.cpp
├── wolfram
│   ├── alpha.cpp
│   ├── CMakeLists.txt
│   └── include
│       └── wolfram
│           └── alpha.hpp
  • 对应的依赖关系如下:answer -> wolffram -> curl_wapper -> libcurl (代码此处不再粘贴)
    • curl_wapper: 对外提供两个接口
      • std::string http_get_string(const std::string &url);
      • std::string url_encode(const std::string &s);
    • wolffram: 对外提供一个接口
      • std::string simple_query(const std::string &appid, const std::string &query);
    • answer 还是和之前一样:
      • find_the_ultimate_answer();
        20220209225506
  • 此时各个目录下的 CMakeLists.txt 如下:
# CMakeLists.txt
add_subdirectory(answer)
add_subdirectory(curl_wrapper)
add_subdirectory(wolfram)
add_executable(answer_app main.cpp)
target_link_libraries(answer_app libanswer)

# answer/CMakeLists.txt
add_library(libanswer STATIC answer.cpp)
target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(libanswer PRIVATE wolfram)

# wolfram/CMakeLists.txt
add_library(wolfram STATIC alpha.cpp)
target_include_directories(wolfram PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(wolfram PRIVATE curl_wrapper)

# curl_wrapper/CMakeLists.txt
find_package(CURL REQUIRED)
add_library(curl_wrapper STATIC curl_wrapper.cpp)
target_include_directories(curl_wrapper
                           PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(curl_wrapper PRIVATE CURL::libcurl)

step9. CMake 参数传递

  • 上文所述使用的公开的 API 常常因为用户的不同而需要不同的设置,那么可以在 CMake 编译时指定相应的参数进行传递 -D<para_name>,但是需要在 CMakeLists.txt 中添加相应的支持。
  • 因为 AppID 在原来的程序中只有 answer 模块才使用到,所以只需修改该模块的 CMakeLists.txt
    • set(WOLFRAM_APPID "" CACHE STRING "WolframAlpha APPID") 注意各个字段的含义
    • target_compile_definitions:要让 C++ 代码能够拿到 CMake 中的变量,可添加编译时宏定义
    • cmake -B build -DWOLFRAM_APPID=xxx:命令行参数传递
    • ccmake:直接 TUI 修改变量传递参数
#[[
创建一个可配置的变量,可以由上级 CMakeLists 或 cmake 命令指定变量值。

这里由于 APPID 是一个应该藏好、不应该放在代码里的值,所以建议在 cmake
命令中通过 -D 参数传入。
#]]
# 格式 参数名
# 默认值
# 变量类型 CACHE 
# 数据类型 STRING
# 参数描述
set(WOLFRAM_APPID
    ""
    CACHE STRING "WolframAlpha APPID")

if(WOLFRAM_APPID STREQUAL "")
    message(SEND_ERROR "WOLFRAM_APPID must not be empty")
endif()

add_library(libanswer STATIC answer.cpp)
target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

#[[
将 WOLFRAM_APPID 添加到编译 .cpp 文件时的 definition 列表,从而
可在 C++ 代码中使用。宏定义名 WOLFRAM_APPID
#]]
target_compile_definitions(libanswer PRIVATE WOLFRAM_APPID="${WOLFRAM_APPID}")

target_link_libraries(libanswer PRIVATE wolfram)

step10. CMake 编译不同类型的库

  • 上面的例子中因为经过充分的解耦,answer 模块的代码已经足够简单了,Modern C++ 可以直接在头文件中实现代码来省略源文件,这种情况被称之为 header-only
#pragma once

#include <string>

#include <wolfram/alpha.hpp>

// header-only 库的所有实现代码均在头文件中

namespace answer {
    namespace v1 {
        int find_the_ultimate_answer() {
            return 42;
        }
    } // namespace v1

    namespace v2 {
        std::string find_the_ultimate_answer() {
            return wolfram::simple_query(WOLFRAM_APPID, "what is the ultimate answer?");
        }
    } // namespace v2

    using namespace v2;
} // namespace answer

  • 针对这种库的编译可以使用特殊的库类型 INTERFACE,相应地也需要修改其他语句
    • 通过 target_xxx 给 INTERFACE library 添加属性都要用 INTERFACE。
add_library(libanswer INTERFACE)
target_include_directories(libanswer
                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_compile_definitions(libanswer INTERFACE WOLFRAM_APPID="${WOLFRAM_APPID}")
target_link_libraries(libanswer INTERFACE wolfram)

step11. 指定 C++ 标准

  • 上文使用的 auto 关键字源于 C++ 11,需要在 CMakeLists.txt 相应地进行指定和约束
  • 但也可以针对 target 要求编译 feature(即指定要使用 C/C++ 的什么特性)target_compile_features
  • 注意两者的区别:
    • CMAKE_CXX_STANDARD 会应用于所有能看到这个变量的 target,而 target_compile_features 只应用于单个 target
    • target_compile_features 可以指定更细粒度的 C++ 特性,例如 cxx_auto_typecxx_lambda 等。这里使用了 cxx20 的标准是为了使用 require 的关键字
# CMakeLists.txt
set(CMAKE_CXX_STANDARD 11)

# answer/CMakeLists.txt
#[[
指明 libanswer 要求 C++20。

这里和直接设置 CMAKE_CXX_STANDARD 的区别是:

    1. 设置 CMAKE_CXX_STANDARD 之后,从设置它的那一级开始
       include 的 subdirectory 都会继承这个变量,且应用于
       所有能看到这个变量的 target;而 target_compile_features
       只应用于单个 target。
    2. target_compile_features 可以指定更细粒度的 C++ 特性,
       例如 cxx_auto_type、cxx_lambda 等。
#]]
target_compile_features(libanswer INTERFACE cxx_std_20)
  • C++20 在本例中的使用,重点 answer::check_the_answer
// main.cpp
int main(int argc, const char *argv[]) {
    for (;;) {
        std::cout << "What is the ultimate answer?" << std::endl;
        std::string answer;
        std::cin >> answer;
        auto expected_answer = answer::find_the_ultimate_answer();
        if (answer::check_the_answer(answer, expected_answer)) {
            std::cout << "Correct!" << std::endl;
            break;
        }
    }
    return 0;
}
  • 具体的 answer::check_the_answer 实现
    namespace v2 {
        std::string find_the_ultimate_answer() {
            return wolfram::simple_query(WOLFRAM_APPID, "what is the ultimate answer?");
        }

        // 下面是非常 fancy 的两个函数,使用了 C++14 的 auto 返回类型、
        // C++17 的 if constexpr 和 C++20 的 constraints。

        namespace impl {
            template <typename T>
            auto to_string(T &&t) {
                if constexpr (requires { std::to_string(t); }) {
                    return std::to_string(std::forward<T>(t));
                } else if constexpr (requires { std::string(t); }) {
                    return std::string(std::forward<T>(t));
                }
            }
        } // namespace impl

        template <typename T, typename U>
        requires requires(T &&t, U &&u) {
            impl::to_string(std::forward<T>(t));
            impl::to_string(std::forward<U>(u));
        }
        auto check_the_answer(T &&given, U &&expected) {
            return impl::to_string(std::forward<T>(given)) == impl::to_string(std::forward<U>(expected));
        }
    } // namespace v2

    using namespace v2;

step12. CTest

  • 现在的目录结构
    root@aep-shunzi:/shunzi/modern-cmake-by-example# tree
    .
    ├── answer
    │   ├── CMakeLists.txt
    │   ├── include
    │   │   └── answer
    │   │       └── answer.hpp
    │   └── tests
    │       ├── CMakeLists.txt
    │       └── test_check_the_answer.cpp
    ├── CMakeLists.txt
    ├── curl_wrapper
    │   ├── CMakeLists.txt
    │   ├── curl_wrapper.cpp
    │   └── include
    │       └── curl_wrapper
    │           └── curl_wrapper.hpp
    ├── main.cpp
    ├── wolfram
    │   ├── alpha.cpp
    │   ├── CMakeLists.txt
    │   └── include
    │       └── wolfram
    │           └── alpha.hpp
    
  • 要使用 CTest 运行 CMake 项目的测试程序,需要在 CMakeLists.txt 添加一些内容:
    • include(CTest) 主 CMakeLists 需要添加对 ctest 的支持
    • BUILD_TESTING 在引入 CTest 之后将会引入一个默认的 CACHE 变量来标识是否编译 Test,默认值为 ON,也可以通过 -D 参数传递来修改
    • add_subdirectory(tests) 在 answer 模块中添加新的测试目录作为子目录,此时的文件结构
    • add_test 添加相应的测试用例
      • <name> 指定本测试的名称
      • Debug/Release 选项可以控制在不同的编译版本下是否进行测试
      • WORKING_DIRECTORY 设置工作路径
      • command 表示可运行程序
      add_test(NAME <name> [CONFIGURATIONS [Debug|Release|...]]
             [WORKING_DIRECTORY dir]
             COMMAND <command> [arg1 [arg2 ...]])
      
  • 使用 CTest 的大体结构
# CMakeLists.txt
cmake_minimum_required(VERSION 3.14) # 提高了 CMake 版本要求
project(answer)
#[[
判断当前目录是否是 CMake 调用的 top-level,如果是,
引入 CTest 支持。

这会引入一个 BUILD_TESTING 选项(类似之前的 CACHE
STRING,这是一个 CACHE BOOL),默认值为 ON,可以在
之后的 CMake 脚本中通过该选项判断是否需要 include
测试用例子目录。
#]]
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
    include(CTest)
endif()

# answer/CMakeLists.txt
if(BUILD_TESTING)
    add_subdirectory(tests)
endif()

# answer/tests/CMakeLists.txt
#[[
add_test 添加 CTest 可以识别到的测试程序,建议使用项目名前缀,
方便在运行测试时和别的第三方库的测试区分。
#]]
add_executable(test_some_func test_some_func.cpp)
add_test(NAME answer.test_some_func COMMAND test_some_func)
  • answer 模块添加的测试代码 answer/tests/test_check_the_answer.cpp
    • 这里使用了 Catch2 框架来进行单元测试,相应地需要添加 Catch2 的依赖
#include <catch2/catch_test_macros.hpp>

#include <answer/answer.hpp>

using namespace answer;

// 使用 Catch2 编写测试用例

TEST_CASE("Can compare string and string", "[check_the_answer]") {
    REQUIRE(check_the_answer("Hello", "Hello") == true);
    REQUIRE(check_the_answer("Hello", "world") == false);
    REQUIRE(check_the_answer("13", std::string("13")) == true);
}

TEST_CASE("Can compare string and integer", "[check_the_answer]") {
    REQUIRE(check_the_answer("13", 13) == true);
    REQUIRE(check_the_answer("13", 14) == false);
    REQUIRE(check_the_answer(13, "13") == true);
    REQUIRE(check_the_answer(13, std::string("13")) == true);
    REQUIRE(check_the_answer(13, std::string("14")) == false);
}

  • 具体的 answer/test 模块的 CMakeLists.txt
    • 除了使用 find_package 找到系统中安装的第三方库,也可通过 CMake 3.11 新增的 FetchContent 功能下载使用第三方库,使用步骤如下
      • include(FetchContent) 首先导入该功能
      • FetchContent_Declare 定义依赖的库对应的信息
        • SOURCE_DIR Declare 中可以指定要安装的目录
      • FetchContent_MakeAvailable 根据定义信息对应下载构建库并进行依赖
        • FetchContent_MakeAvailable 要求 CMake 3.14,如果要支持更旧版本,或者需要更细粒度的控制,可以使用如下替代:
          FetchContent_GetProperties(catch2)
          if(NOT catch2_POPULATED)
              FetchContent_Populate(catch2)
              add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR})
          endif()
          
      • target_link_libraries(${TEST_NAME} PRIVATE Catch2::Catch2WithMain) 相应地在后面依赖该库
# 导入 FetchContent 相关命令
include(FetchContent)

# 描述如何获取 Catch2
FetchContent_Declare(
    catch2 # 建议使用全小写
    GIT_REPOSITORY https://github.com/catchorg/Catch2.git
    GIT_TAG v3.0.0-preview3)

# 一条龙地下载、构建 Catch2
FetchContent_MakeAvailable(catch2)

#[[
FetchContent 要求 CMake 3.11 或更高版本,在此之间
可以使用 Git submodule + add_subdirectory 的方式
使用没有安装在系统中的第三方库。即使支持 FetchContent
也可以选择使用 Git submodule,各有优劣。

FetchContent_MakeAvailable 要求 CMake 3.14,如果
要支持更旧版本,或者需要更细粒度的控制,可以使用如下替代:

    FetchContent_GetProperties(catch2)
    if(NOT catch2_POPULATED)
        FetchContent_Populate(catch2)
        add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR})
    endif()
#]]

# macro(宏)类似于 C/C++ 中的宏
macro(answer_add_test TEST_NAME)
    add_executable(${TEST_NAME} ${ARGN}) # ${ARGN} 类似于 C/C++ 中的 __VA_ARGS__
    #[[
    add_test 添加 CTest 可以识别到的测试程序,建议使用项目名前缀,
    方便在运行测试时和别的第三方库的测试区分。
    #]]
    add_test(NAME answer.${TEST_NAME} COMMAND ${TEST_NAME})
    target_link_libraries(${TEST_NAME} PRIVATE libanswer)
    #[[
    链接 Catch2::Catch2WithMain 以使用 Catch2 提供的宏,链接
    Catch2WithMain 时,测试程序中不需要手动编写 main 函数。
    #]]
    target_link_libraries(${TEST_NAME} PRIVATE Catch2::Catch2WithMain)
endmacro()

# 调用上面的 macro 添加测试程序
answer_add_test(test_check_the_answer test_check_the_answer.cpp)

  • 原 CMakeLists.txt 中使用了一种相对特殊的语法。macro 类似于定义了一个宏。
    • macro(answer_add_test TEST_NAME) 对应定义了函数名 answer_add_test 和函数参数 TEST_NAME
    • add_executable(${TEST_NAME} ${ARGN}) 本例中相应地使用对应的参数创建了对应的测试程序,其中 ${ARGN} 类似于 C/C++ 中的 __VA_ARGS__,即可变数量的参数数组
    • add_test(NAME answer.${TEST_NAME} COMMAND ${TEST_NAME}) 添加 CTest 可以识别到的测试程序
    • target_link_libraries(${TEST_NAME} PRIVATE libanswer) 给相应的测试程序链接对应的库
    • answer_add_test(test_check_the_answer test_check_the_answer.cpp) 相应地调用对应的宏定义,本例中对应了一个测试程序,test_check_the_answer 以及其其所依赖的源文件 test_check_the_answer.cpp
  • 格式
macro(answer_add_test TEST_NAME)
    add_executable(${TEST_NAME} ${ARGN}) # ${ARGN} 类似于 C/C++ 中的 __VA_ARGS__
    add_test(NAME answer.${TEST_NAME} COMMAND ${TEST_NAME})
    target_link_libraries(${TEST_NAME} PRIVATE libanswer)
    target_link_libraries(${TEST_NAME} PRIVATE Catch2::Catch2WithMain)
endmacro()

answer_add_test(test_check_the_answer test_check_the_answer.cpp)
answer_add_test(test_another_function test_another_function.cpp)
  • ctest --test-dir build -R "^answer." 运行 ctest,制定相应的目录和对应的 test 名称

macro 和 function 的区别

  • CMake语法—宏和函数(macro vs function)
  • 在 CMake 中也可以定义 function,那么 CMake 中的 function 和 macro 的区别其实是和 C/C++ 中的宏定义与函数的区别是一致的。
    • 函数会产生新作用域;宏是把执行代码替换到调用位置
    • 函数内可以使用return;宏中不建议使用return
    • 在函数中可以调用宏
    • 函数中有一些特有的默认变量
      • ${CMAKE_CURRENT_FUNCTION} 当前函数名称
      • ${CMAKE_CURRENT_FUNCTION_LIST_DIR} 当前函数路径
      • ${CMAKE_CURRENT_FUNCTION_LIST_FILE} 当前函数所属文件
      • ${CMAKE_CURRENT_FUNCTION_LIST_LINE} 当前函数定义的起始行数

step13. 重新使用 Makefile 来提升效率

  • 使用 Makefile 的简单特性来简化编译运行过程
    • make build WOLFRAM_APPID=xxx 简化 build 过程,并传递相关参数
    • make test 简化 test 运行,执行 CTest
    • make run 简化运行过程,直接执行
    • make clean 简化重编译的过程,删除 build 目录
WOLFRAM_APPID :=

.PHONY: build
build: configure
	cmake --build build

.PHONY: configure
configure:
	cmake -B build -DWOLFRAM_APPID=${WOLFRAM_APPID}

.PHONY: run
run:
	./build/answer_app

.PHONY: test
test:
	ctest --test-dir build -R "^answer."

.PHONY: clean
clean:
	rm -rf build

#
# 可以使用 Make 来更方便地调用 CMake 命令:
#
#     make build WOLFRAM_APPID=xxx
#     make test
#     make run
#     make clean
#

Other

  • CMake 本质也是代码,应该格式化,格式化工具:https://github.com/cheshirekow/cmake_format
  • Linux 内核是可以修改并替换的,只需要下载内核源码,然后:
$ make defconfig # 有各种 xxxconfig
$ make menuconfig # TUI 界面修改 config
$ make # 构建 Linux 内核
$ make modules_install # 安装内核模块
$ make install # 安装内核

一个具体的示例 RocksDB

  • 命令介绍
    • list (subcommand <list> [args...])subcommand 为具体的列表操作子命令,GET/APPEND/INSERT/REMOVE_ITEM/REMOVE_AT/REMOVE_DUPLICATES/REVERSE/SORT<list> 为待操作的列表变量,[args...] 为对列表变量操作需要使用的参数表,不同的子命令对应的参数也不一致。
      • CMAKE_MODULE_PATH 默认情况下为空,它是由项目设置的。
    • include: 用来载入并运行来自于文件或模块的 CMake 代码。即引入其他的 cmake 配置代码,这里主要是承接上一个命令对应的其他 cmake 模块
      20220212215043
      • include(ReadVersion) 对应了上图中的 ReadVersion 模块,该模块定义了获取当前 RocksDB 版本的 function get_rocksdb_version,该函数将从 "${CMAKE_CURRENT_SOURCE_DIR}/include/rocksdb/version.h 文件中读取对应的版本号,使用正则表达式去匹配,然后得到相应的返回值 version_var
      • include(GoogleTest) 对应了 CMake 中自带的 GoogleTest 模块,https://cmake.org/cmake/help/latest/module/GoogleTest.html
    • project 描述项目基本信息,名称,版本,语言
    • CMP0042 https://cmake.org/cmake/help/v3.0/policy/CMP0042.html
    • CMAKE_BUILD_TYPE CMake 编译的相关配置,根据时候被作为参数传递进来以及是否包含 git 目录来采取不同的设置,可能的配置如下。设置完后 set cache string 变量 CMAKE_BUILD_TYPE
      • Debug:Adds the -g flag
      • Release:Adds the -O3 -DNDEBUG flags to the compiler
      • RelWithDebInfo:Adds -O2 -g -DNDEBUG flags
      • MinSizeRel:Adds -Os -DNDEBUG
    • find_program 寻找相应的程序,并将结果存储在变量中。
    • option 定义编译选项,可以在编译时指定参数 -Dxxx 传递
    • add_definitions 为源文件的编译添加由-D定义的标志
    • $ENV{NAME} 调用系统的环境变量
    • CMAKE_SYSTEM_NAME:CMake 要构建的操作系统的名称
    • CMAKE_CXX_STANDARD:CXX 标准设置
    • include(CMakeDependentOption):引入 CMAKE_DEPENDENT_OPTION 对应的模块
    • string cmake 中的字符串操作 https://cmake.org/cmake/help/latest/command/string.html
    • execute_process 执行对应的系统命令。注意几个变量的设置
      • RESULT_VARIABLE:变量被设置为包含子进程的运行结果。返回码将是一个来自于最后一个子进程的整数或者一个错误描述字符串。
      • OUTPUT_VARIABLE, ERROR_VARIABLE:命名的变量将被分别设置为标准输出和标准错误管道的内容。如果为2个管道命名了相同的名字,他们的输出将按照产生顺序被合并
  • CMakeLists.txt
# Linux:
#
# 1. Install a recent toolchain if you're on a older distro. C++17 required (GCC >= 7, Clang >= 5)
# 2. mkdir build; cd build
# 3. cmake ..
# 4. make -j

cmake_minimum_required(VERSION 3.10)
# 添加模块目录到 CMAKE_MODULE_PATH
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
# 引入 ReadVersion 对应的 function
include(ReadVersion)
# 引入 CMake 自带的 GoogleTest
include(GoogleTest)
# 获取对应的 RocksDB 版本信息,rocksdb_VERSION
get_rocksdb_version(rocksdb_VERSION)
# 描述项目的基础信息
project(rocksdb
  VERSION ${rocksdb_VERSION}
  LANGUAGES CXX C ASM)

# MACOSX_RPATH 默认启用 
if(POLICY CMP0042)
  cmake_policy(SET CMP0042 NEW)
endif()

# 如果没有指定 CMAKE_BUILD_TYPE
if(NOT CMAKE_BUILD_TYPE)
  # 判断是否包含 git 目录,包含的话开启 debug 模式,否则开启 RelWithDebInfo
  if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
    set(default_build_type "Debug")
  else()
    set(default_build_type "RelWithDebInfo")
  endif()
  # 设置相应的参数
  set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
    "Default BUILD_TYPE is ${default_build_type}" FORCE)
endif()

# 查找编译器程序 ccache 编译器缓存
find_program(CCACHE_FOUND ccache)
# 如果找到了该程序,设置属性,将 ccache 作为编译命令和链接命令的启动器
if(CCACHE_FOUND)
  set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
  set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)

# 定义几个依赖库的编译选型,并设置默认值
option(WITH_JEMALLOC "build with JeMalloc" OFF)
option(WITH_LIBURING "build with liburing" ON)
option(WITH_SNAPPY "build with SNAPPY" OFF)
option(WITH_LZ4 "build with lz4" OFF)
option(WITH_ZLIB "build with zlib" OFF)
option(WITH_ZSTD "build with zstd" OFF)
option(WITH_WINDOWS_UTF8_FILENAMES "use UTF8 as characterset for opening files, regardles of the system code page" OFF)
if (WITH_WINDOWS_UTF8_FILENAMES)
  add_definitions(-DROCKSDB_WINDOWS_UTF8_FILENAMES)
endif()

# 如果环境变量中配置了 CIRCLECI,一款持续集成的工具
if ($ENV{CIRCLECI})
  message(STATUS "Build for CircieCI env, a few tests may be disabled")
  add_definitions(-DCIRCLECI)
endif()

# 如果操作系统是 Linux 或者 win
# third-party/folly is only validated to work on Linux and Windows for now.
# So only turn it on there by default.
if(CMAKE_SYSTEM_NAME MATCHES "Linux|Windows")
  # 判断 MSVC 版本,并对 Folly 进行配置(folly, Facebook Open-source Library)
  if(MSVC AND MSVC_VERSION LESS 1910)
    # Folly does not compile with MSVC older than VS2017
    option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" OFF)
  else()
    option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" ON)
  endif()
else()
  option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" OFF)
endif()

# 如果没制定 CXX 标准,指定 C++17
if( NOT DEFINED CMAKE_CXX_STANDARD )
  set(CMAKE_CXX_STANDARD 17)
endif()

# 引入专门的条件 Option 模块,CMAKE_DEPENDENT_OPTION
include(CMakeDependentOption)

# 根据不同的操作系统/不同的编译器为 option 赋值
if(MSVC)
  option(WITH_GFLAGS "build with GFlags" OFF)
  option(WITH_XPRESS "build with windows built in compression" OFF)
  include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
else()
  if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" AND NOT CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
    # FreeBSD has jemalloc as default malloc
    # but it does not have all the jemalloc files in include/...
    set(WITH_JEMALLOC ON)
  else()
    if(WITH_JEMALLOC)
      # 查询 Jemalloc 的库
      find_package(JeMalloc REQUIRED)
      # 为源文件的编译添加由-D定义的标志。
      add_definitions(-DROCKSDB_JEMALLOC -DJEMALLOC_NO_DEMANGLE)
      # 将对应的依赖库名称追加到变量 THIRDPARTY_LIBS
      list(APPEND THIRDPARTY_LIBS JeMalloc::JeMalloc)
    endif()
  endif()

  if(MINGW)
    option(WITH_GFLAGS "build with GFlags" OFF)
  else()
    option(WITH_GFLAGS "build with GFlags" ON)
  endif()
  set(GFLAGS_LIB)
  if(WITH_GFLAGS)
    # Config with namespace available since gflags 2.2.2
    option(GFLAGS_USE_TARGET_NAMESPACE "Use gflags import target with namespace." ON)
    find_package(gflags CONFIG)
    if(gflags_FOUND)
      if(TARGET ${GFLAGS_TARGET})
        # Config with GFLAGS_TARGET available since gflags 2.2.0
        set(GFLAGS_LIB ${GFLAGS_TARGET})
      else()
        # Config with GFLAGS_LIBRARIES available since gflags 2.1.0
        set(GFLAGS_LIB ${gflags_LIBRARIES})
      endif()
    else()
      find_package(gflags REQUIRED)
      set(GFLAGS_LIB gflags::gflags)
    endif()
    include_directories(${GFLAGS_INCLUDE_DIR})
    list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
    add_definitions(-DGFLAGS=1)
  endif()

  # 如果开启了 WITH_SNAPPY
  if(WITH_SNAPPY)
    find_package(Snappy CONFIG)
    if(NOT Snappy_FOUND)
      find_package(Snappy REQUIRED)
    endif()
    add_definitions(-DSNAPPY)
    list(APPEND THIRDPARTY_LIBS Snappy::snappy)
  endif()

  if(WITH_ZLIB)
    find_package(ZLIB REQUIRED)
    add_definitions(-DZLIB)
    list(APPEND THIRDPARTY_LIBS ZLIB::ZLIB)
  endif()

  option(WITH_BZ2 "build with bzip2" OFF)
  if(WITH_BZ2)
    find_package(BZip2 REQUIRED)
    add_definitions(-DBZIP2)
    if(BZIP2_INCLUDE_DIRS)
      include_directories(${BZIP2_INCLUDE_DIRS})
    else()
      include_directories(${BZIP2_INCLUDE_DIR})
    endif()
    list(APPEND THIRDPARTY_LIBS ${BZIP2_LIBRARIES})
  endif()

  if(WITH_LZ4)
    find_package(lz4 REQUIRED)
    add_definitions(-DLZ4)
    list(APPEND THIRDPARTY_LIBS lz4::lz4)
  endif()

  if(WITH_ZSTD)
    find_package(zstd REQUIRED)
    add_definitions(-DZSTD)
    include_directories(${ZSTD_INCLUDE_DIR})
    list(APPEND THIRDPARTY_LIBS zstd::zstd)
  endif()
endif()

# 生成对应的时间戳 TS
string(TIMESTAMP TS "%Y-%m-%d %H:%M:%S" UTC)
# 设置相应的参数
set(BUILD_DATE "${TS}" CACHE STRING "the time we first built rocksdb")

# 搜索依赖 Git
find_package(Git)

# 如果找到了 Git 并且当且目录包含 Git 目录
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
  # 执行系统命令 git rev-parse HEAD,得到了对应的 CommitID 保存到了 GIT_SHA  (标准输出)
  execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_SHA COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD )
  # 执行命令 git diff-index HEAD --quiet 比较树与工作树或索引并禁止输出,保存命令执行结果到 GIT_MOD
  execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE GIT_MOD COMMAND "${GIT_EXECUTABLE}" diff-index HEAD --quiet)
  # 执行命令 git log -1 --date=format:"%Y-%m-%d %T" --format="%ad" 输出最近的 git 提交的时间到 GIT_DATE
  execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DATE COMMAND "${GIT_EXECUTABLE}" log -1 --date=format:"%Y-%m-%d %T" --format="%ad")
  # 执行命令 git symbolic-ref -q --short HEAD OUTPUT_STRIP_TRAILING_WHITESPACE
  # 输出对应的 tag 到 GIT_TAG 和命令执行结果 rv
  execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG RESULT_VARIABLE rv COMMAND "${GIT_EXECUTABLE}" symbolic-ref -q --short HEAD OUTPUT_STRIP_TRAILING_WHITESPACE)
  # 如果 rv != 0
  if (rv AND NOT rv EQUAL 0)
    # git describe --tags --exact-match OUTPUT_STRIP_TRAILING_WHITESPACE
    # 结果输出到 GIT_TAG 
    execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG COMMAND "${GIT_EXECUTABLE}" describe --tags --exact-match OUTPUT_STRIP_TRAILING_WHITESPACE)
  endif()
else()
  set(GIT_SHA 0)
  set(GIT_MOD 1)
endif()
# 使用正则表达式格式化对应的 GIT_SHA 和 GIT_DATE
string(REGEX REPLACE "[^0-9a-fA-F]+" "" GIT_SHA "${GIT_SHA}")
string(REGEX REPLACE "[^0-9: /-]+" "" GIT_DATE "${GIT_DATE}")

option(WITH_MD_LIBRARY "build with MD" ON)
if(WIN32 AND MSVC)
  if(WITH_MD_LIBRARY)
    set(RUNTIME_LIBRARY "MD")
  else()
    set(RUNTIME_LIBRARY "MT")
  endif()
endif()

set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)

if(MSVC)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324")
else()
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing")
  if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes")
  endif()
  if(MINGW)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format -fno-asynchronous-unwind-tables")
    add_definitions(-D_POSIX_C_SOURCE=1)
  endif()
  if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
    include(CheckCXXCompilerFlag)
    CHECK_CXX_COMPILER_FLAG("-momit-leaf-frame-pointer" HAVE_OMIT_LEAF_FRAME_POINTER)
    if(HAVE_OMIT_LEAF_FRAME_POINTER)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -momit-leaf-frame-pointer")
    endif()
  endif()
endif()

include(CheckCCompilerFlag)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
  CHECK_C_COMPILER_FLAG("-mcpu=power9" HAS_POWER9)
  if(HAS_POWER9)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power9 -mtune=power9")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power9 -mtune=power9")
  else()
    CHECK_C_COMPILER_FLAG("-mcpu=power8" HAS_POWER8)
    if(HAS_POWER8)
      set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8 -mtune=power8")
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8 -mtune=power8")
    endif(HAS_POWER8)
  endif(HAS_POWER9)
  CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
  if(HAS_ALTIVEC)
    message(STATUS " HAS_ALTIVEC yes")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec")
  endif(HAS_ALTIVEC)
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")

if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
        CHECK_C_COMPILER_FLAG("-march=armv8-a+crc+crypto" HAS_ARMV8_CRC)
  if(HAS_ARMV8_CRC)
    message(STATUS " HAS_ARMV8_CRC yes")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
  endif(HAS_ARMV8_CRC)
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")

if(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
  CHECK_C_COMPILER_FLAG("-march=native" HAS_S390X_MARCH_NATIVE)
  if(HAS_S390X_MARCH_NATIVE)
    message(STATUS " HAS_S390X_MARCH_NATIVE yes")
  endif(HAS_S390X_MARCH_NATIVE)
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")

option(PORTABLE "build a portable binary" OFF)
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
option(FORCE_AVX "force building with AVX, even when PORTABLE=ON" OFF)
option(FORCE_AVX2 "force building with AVX2, even when PORTABLE=ON" OFF)
if(PORTABLE)
  # MSVC does not need a separate compiler flag to enable SSE4.2; if nmmintrin.h
  # is available, it is available by default.
  if(FORCE_SSE42 AND NOT MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -mpclmul")
  endif()
  if(MSVC)
    if(FORCE_AVX)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX")
    endif()
    # MSVC automatically enables BMI / lzcnt with AVX2.
    if(FORCE_AVX2)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
    endif()
  else()
    if(FORCE_AVX)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx")
    endif()
    if(FORCE_AVX2)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -mbmi -mlzcnt")
    endif()
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x")
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
    endif()
  endif()
else()
  if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
  else()
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x" AND NOT HAS_S390X_MARCH_NATIVE)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
    elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
    endif()
  endif()
endif()

include(CheckCXXSourceCompiles)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
if(NOT MSVC)
  set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
endif()

CHECK_CXX_SOURCE_COMPILES("
#include <cstdint>
#include <nmmintrin.h>
#include <wmmintrin.h>
int main() {
  volatile uint32_t x = _mm_crc32_u32(0, 0);
  const auto a = _mm_set_epi64x(0, 0);
  const auto b = _mm_set_epi64x(0, 0);
  const auto c = _mm_clmulepi64_si128(a, b, 0x00);
  auto d = _mm_cvtsi128_si64(c);
}
" HAVE_SSE42)
if(HAVE_SSE42)
  add_definitions(-DHAVE_SSE42)
  add_definitions(-DHAVE_PCLMUL)
elseif(FORCE_SSE42)
  message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled")
endif()

# Check if -latomic is required or not
if (NOT MSVC)
  set(CMAKE_REQUIRED_FLAGS "--std=c++17")
  CHECK_CXX_SOURCE_COMPILES("
#include <atomic>
std::atomic<uint64_t> x(0);
int main() {
  uint64_t i = x.load(std::memory_order_relaxed);
  bool b = x.is_lock_free();
  return 0;
}
" BUILTIN_ATOMIC)
  if (NOT BUILTIN_ATOMIC)
    #TODO: Check if -latomic exists
    list(APPEND THIRDPARTY_LIBS atomic)
  endif()
endif()

if (WITH_LIBURING)
  find_package(uring)
  if (uring_FOUND)
    add_definitions(-DROCKSDB_IOURING_PRESENT)
    list(APPEND THIRDPARTY_LIBS uring::uring)
  endif()
endif()

# Reset the required flags
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})

# thread_local is part of C++11 and later (TODO: clean up this define)
add_definitions(-DROCKSDB_SUPPORT_THREAD_LOCAL)

option(WITH_IOSTATS_CONTEXT "Enable IO stats context" ON)
if (NOT WITH_IOSTATS_CONTEXT)
  add_definitions(-DNIOSTATS_CONTEXT)
endif()

option(WITH_PERF_CONTEXT "Enable perf context" ON)
if (NOT WITH_PERF_CONTEXT)
  add_definitions(-DNPERF_CONTEXT)
endif()

option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
if(FAIL_ON_WARNINGS)
  if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX")
  else() # assume GCC
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
  endif()
endif()

option(WITH_ASAN "build with ASAN" OFF)
if(WITH_ASAN)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
  if(WITH_JEMALLOC)
    message(FATAL "ASAN does not work well with JeMalloc")
  endif()
endif()

option(WITH_TSAN "build with TSAN" OFF)
if(WITH_TSAN)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread -pie")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -fPIC")
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread -fPIC")
  if(WITH_JEMALLOC)
    message(FATAL "TSAN does not work well with JeMalloc")
  endif()
endif()

option(WITH_UBSAN "build with UBSAN" OFF)
if(WITH_UBSAN)
  add_definitions(-DROCKSDB_UBSAN_RUN)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined")
  if(WITH_JEMALLOC)
    message(FATAL "UBSAN does not work well with JeMalloc")
  endif()
endif()

option(WITH_NUMA "build with NUMA policy support" OFF)
if(WITH_NUMA)
  find_package(NUMA REQUIRED)
  add_definitions(-DNUMA)
  include_directories(${NUMA_INCLUDE_DIR})
  list(APPEND THIRDPARTY_LIBS NUMA::NUMA)
endif()

option(WITH_TBB "build with Threading Building Blocks (TBB)" OFF)
if(WITH_TBB)
  find_package(TBB REQUIRED)
  add_definitions(-DTBB)
  list(APPEND THIRDPARTY_LIBS TBB::TBB)
endif()

# Stall notifications eat some performance from inserts
option(DISABLE_STALL_NOTIF "Build with stall notifications" OFF)
if(DISABLE_STALL_NOTIF)
  add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION)
endif()

option(WITH_DYNAMIC_EXTENSION "build with dynamic extension support" OFF)
if(NOT WITH_DYNAMIC_EXTENSION)
  add_definitions(-DROCKSDB_NO_DYNAMIC_EXTENSION)
endif()

option(ASSERT_STATUS_CHECKED "build with assert status checked" OFF)
if (ASSERT_STATUS_CHECKED)
  message(STATUS "Build with assert status checked")
  add_definitions(-DROCKSDB_ASSERT_STATUS_CHECKED)
endif()

if(DEFINED USE_RTTI)
  if(USE_RTTI)
    message(STATUS "Enabling RTTI")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DROCKSDB_USE_RTTI")
  else()
    if(MSVC)
      message(STATUS "Disabling RTTI in Release builds. Always on in Debug.")
      set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
      set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
    else()
      message(STATUS "Disabling RTTI in Release builds")
      set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti")
      set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
    endif()
  endif()
else()
  message(STATUS "Enabling RTTI in Debug builds only (default)")
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
  if(MSVC)
     set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
  else()
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
  endif()
endif()

# Used to run CI build and tests so we can run faster
option(OPTDBG "Build optimized debug build with MSVC" OFF)
option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON)
if(MSVC)
  if(OPTDBG)
    message(STATUS "Debug optimization is enabled")
    set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
  else()
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")

    # Minimal Build is deprecated after MSVC 2015
    if( MSVC_VERSION GREATER 1900 )
      set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
    else()
      set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
    endif()

  endif()
  if(WITH_RUNTIME_DEBUG)
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
  else()
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}")
  endif()
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}")

  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
endif()

if(CMAKE_COMPILER_IS_GNUCXX)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp")
endif()

option(ROCKSDB_LITE "Build RocksDBLite version" OFF)
if(ROCKSDB_LITE)
  add_definitions(-DROCKSDB_LITE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -Os")
endif()

if(CMAKE_SYSTEM_NAME MATCHES "Cygwin")
  add_definitions(-fno-builtin-memcmp -DCYGWIN)
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
  add_definitions(-DOS_MACOSX)
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
  add_definitions(-DOS_LINUX)
elseif(CMAKE_SYSTEM_NAME MATCHES "SunOS")
  add_definitions(-DOS_SOLARIS)
elseif(CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
  add_definitions(-DOS_GNU_KFREEBSD)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
  add_definitions(-DOS_FREEBSD)
elseif(CMAKE_SYSTEM_NAME MATCHES "NetBSD")
  add_definitions(-DOS_NETBSD)
elseif(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
  add_definitions(-DOS_OPENBSD)
elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly")
  add_definitions(-DOS_DRAGONFLYBSD)
elseif(CMAKE_SYSTEM_NAME MATCHES "Android")
  add_definitions(-DOS_ANDROID)
elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
  add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64 -DNOMINMAX)
  if(MINGW)
    add_definitions(-D_WIN32_WINNT=_WIN32_WINNT_VISTA)
  endif()
endif()

if(NOT WIN32)
  add_definitions(-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX)
endif()

option(WITH_FALLOCATE "build with fallocate" ON)
if(WITH_FALLOCATE)
  CHECK_CXX_SOURCE_COMPILES("
#include <fcntl.h>
#include <linux/falloc.h>
int main() {
 int fd = open(\"/dev/null\", 0);
 fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024);
}
" HAVE_FALLOCATE)
  if(HAVE_FALLOCATE)
    add_definitions(-DROCKSDB_FALLOCATE_PRESENT)
  endif()
endif()

CHECK_CXX_SOURCE_COMPILES("
#include <fcntl.h>
int main() {
  int fd = open(\"/dev/null\", 0);
  sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE);
}
" HAVE_SYNC_FILE_RANGE_WRITE)
if(HAVE_SYNC_FILE_RANGE_WRITE)
  add_definitions(-DROCKSDB_RANGESYNC_PRESENT)
endif()

CHECK_CXX_SOURCE_COMPILES("
#include <pthread.h>
int main() {
  (void) PTHREAD_MUTEX_ADAPTIVE_NP;
}
" HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
if(HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
  add_definitions(-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX)
endif()

include(CheckCXXSymbolExists)
if(CMAKE_SYSTEM_NAME MATCHES "^FreeBSD")
  check_cxx_symbol_exists(malloc_usable_size malloc_np.h HAVE_MALLOC_USABLE_SIZE)
else()
  check_cxx_symbol_exists(malloc_usable_size malloc.h HAVE_MALLOC_USABLE_SIZE)
endif()
if(HAVE_MALLOC_USABLE_SIZE)
  add_definitions(-DROCKSDB_MALLOC_USABLE_SIZE)
endif()

check_cxx_symbol_exists(sched_getcpu sched.h HAVE_SCHED_GETCPU)
if(HAVE_SCHED_GETCPU)
  add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT)
endif()

check_cxx_symbol_exists(getauxval auvx.h HAVE_AUXV_GETAUXVAL)
if(HAVE_AUXV_GETAUXVAL)
  add_definitions(-DROCKSDB_AUXV_GETAUXVAL_PRESENT)
endif()

check_cxx_symbol_exists(F_FULLFSYNC "fcntl.h" HAVE_FULLFSYNC)
if(HAVE_FULLFSYNC)
  add_definitions(-DHAVE_FULLFSYNC)
endif()

include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/include)
if(WITH_FOLLY_DISTRIBUTED_MUTEX)
  include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
endif()
find_package(Threads REQUIRED)

# Main library source code

set(SOURCES
        cache/cache.cc
        cache/cache_entry_roles.cc
        cache/cache_key.cc
        cache/cache_reservation_manager.cc
        cache/clock_cache.cc
        cache/lru_cache.cc
        cache/sharded_cache.cc
        db/arena_wrapped_db_iter.cc
        db/blob/blob_fetcher.cc
        db/blob/blob_file_addition.cc
        db/blob/blob_file_builder.cc
        db/blob/blob_file_cache.cc
        db/blob/blob_file_garbage.cc
        db/blob/blob_file_meta.cc
        db/blob/blob_file_reader.cc
        db/blob/blob_garbage_meter.cc
        db/blob/blob_log_format.cc
        db/blob/blob_log_sequential_reader.cc
        db/blob/blob_log_writer.cc
        db/blob/prefetch_buffer_collection.cc
        db/builder.cc
        db/c.cc
        db/column_family.cc
        db/compaction/compaction.cc
        db/compaction/compaction_iterator.cc
        db/compaction/compaction_picker.cc
        db/compaction/compaction_job.cc
        db/compaction/compaction_picker_fifo.cc
        db/compaction/compaction_picker_level.cc
        db/compaction/compaction_picker_universal.cc
        db/compaction/sst_partitioner.cc
        db/convenience.cc
        db/db_filesnapshot.cc
        db/db_impl/compacted_db_impl.cc
        db/db_impl/db_impl.cc
        db/db_impl/db_impl_write.cc
        db/db_impl/db_impl_compaction_flush.cc
        db/db_impl/db_impl_files.cc
        db/db_impl/db_impl_open.cc
        db/db_impl/db_impl_debug.cc
        db/db_impl/db_impl_experimental.cc
        db/db_impl/db_impl_readonly.cc
        db/db_impl/db_impl_secondary.cc
        db/db_info_dumper.cc
        db/db_iter.cc
        db/dbformat.cc
        db/error_handler.cc
        db/event_helpers.cc
        db/experimental.cc
        db/external_sst_file_ingestion_job.cc
        db/file_indexer.cc
        db/flush_job.cc
        db/flush_scheduler.cc
        db/forward_iterator.cc
        db/import_column_family_job.cc
        db/internal_stats.cc
        db/logs_with_prep_tracker.cc
        db/log_reader.cc
        db/log_writer.cc
        db/malloc_stats.cc
        db/memtable.cc
        db/memtable_list.cc
        db/merge_helper.cc
        db/merge_operator.cc
        db/output_validator.cc
        db/periodic_work_scheduler.cc
        db/range_del_aggregator.cc
        db/range_tombstone_fragmenter.cc
        db/repair.cc
        db/snapshot_impl.cc
        db/table_cache.cc
        db/table_properties_collector.cc
        db/transaction_log_impl.cc
        db/trim_history_scheduler.cc
        db/version_builder.cc
        db/version_edit.cc
        db/version_edit_handler.cc
        db/version_set.cc
        db/wal_edit.cc
        db/wal_manager.cc
        db/write_batch.cc
        db/write_batch_base.cc
        db/write_controller.cc
        db/write_thread.cc
        env/composite_env.cc
        env/env.cc
        env/env_chroot.cc
        env/env_encryption.cc
        env/file_system.cc
        env/file_system_tracer.cc
        env/fs_remap.cc
        env/mock_env.cc
        env/unique_id_gen.cc
        file/delete_scheduler.cc
        file/file_prefetch_buffer.cc
        file/file_util.cc
        file/filename.cc
        file/line_file_reader.cc
        file/random_access_file_reader.cc
        file/read_write_util.cc
        file/readahead_raf.cc
        file/sequence_file_reader.cc
        file/sst_file_manager_impl.cc
        file/writable_file_writer.cc
        logging/auto_roll_logger.cc
        logging/event_logger.cc
        logging/log_buffer.cc
        memory/arena.cc
        memory/concurrent_arena.cc
        memory/jemalloc_nodump_allocator.cc
        memory/memkind_kmem_allocator.cc
        memory/memory_allocator.cc
        memtable/alloc_tracker.cc
        memtable/hash_linklist_rep.cc
        memtable/hash_skiplist_rep.cc
        memtable/skiplistrep.cc
        memtable/vectorrep.cc
        memtable/write_buffer_manager.cc
        monitoring/histogram.cc
        monitoring/histogram_windowing.cc
        monitoring/in_memory_stats_history.cc
        monitoring/instrumented_mutex.cc
        monitoring/iostats_context.cc
        monitoring/perf_context.cc
        monitoring/perf_level.cc
        monitoring/persistent_stats_history.cc
        monitoring/statistics.cc
        monitoring/thread_status_impl.cc
        monitoring/thread_status_updater.cc
        monitoring/thread_status_util.cc
        monitoring/thread_status_util_debug.cc
        options/cf_options.cc
        options/configurable.cc
        options/customizable.cc
        options/db_options.cc
        options/options.cc
        options/options_helper.cc
        options/options_parser.cc
        port/stack_trace.cc
        table/adaptive/adaptive_table_factory.cc
        table/block_based/binary_search_index_reader.cc
        table/block_based/block.cc
        table/block_based/block_based_filter_block.cc
        table/block_based/block_based_table_builder.cc
        table/block_based/block_based_table_factory.cc
        table/block_based/block_based_table_iterator.cc
        table/block_based/block_based_table_reader.cc
        table/block_based/block_builder.cc
        table/block_based/block_prefetcher.cc
        table/block_based/block_prefix_index.cc
        table/block_based/data_block_hash_index.cc
        table/block_based/data_block_footer.cc
        table/block_based/filter_block_reader_common.cc
        table/block_based/filter_policy.cc
        table/block_based/flush_block_policy.cc
        table/block_based/full_filter_block.cc
        table/block_based/hash_index_reader.cc
        table/block_based/index_builder.cc
        table/block_based/index_reader_common.cc
        table/block_based/parsed_full_filter_block.cc
        table/block_based/partitioned_filter_block.cc
        table/block_based/partitioned_index_iterator.cc
        table/block_based/partitioned_index_reader.cc
        table/block_based/reader_common.cc
        table/block_based/uncompression_dict_reader.cc
        table/block_fetcher.cc
        table/cuckoo/cuckoo_table_builder.cc
        table/cuckoo/cuckoo_table_factory.cc
        table/cuckoo/cuckoo_table_reader.cc
        table/format.cc
        table/get_context.cc
        table/iterator.cc
        table/merging_iterator.cc
        table/meta_blocks.cc
        table/persistent_cache_helper.cc
        table/plain/plain_table_bloom.cc
        table/plain/plain_table_builder.cc
        table/plain/plain_table_factory.cc
        table/plain/plain_table_index.cc
        table/plain/plain_table_key_coding.cc
        table/plain/plain_table_reader.cc
        table/sst_file_dumper.cc
        table/sst_file_reader.cc
        table/sst_file_writer.cc
        table/table_factory.cc
        table/table_properties.cc
        table/two_level_iterator.cc
        table/unique_id.cc
        test_util/sync_point.cc
        test_util/sync_point_impl.cc
        test_util/testutil.cc
        test_util/transaction_test_util.cc
        tools/block_cache_analyzer/block_cache_trace_analyzer.cc
        tools/dump/db_dump_tool.cc
        tools/io_tracer_parser_tool.cc
        tools/ldb_cmd.cc
        tools/ldb_tool.cc
        tools/sst_dump_tool.cc
        tools/trace_analyzer_tool.cc
        trace_replay/block_cache_tracer.cc
        trace_replay/io_tracer.cc
        trace_replay/trace_record_handler.cc
        trace_replay/trace_record_result.cc
        trace_replay/trace_record.cc
        trace_replay/trace_replay.cc
        util/coding.cc
        util/compaction_job_stats_impl.cc
        util/comparator.cc
        util/compression_context_cache.cc
        util/concurrent_task_limiter_impl.cc
        util/crc32c.cc
        util/dynamic_bloom.cc
        util/hash.cc
        util/murmurhash.cc
        util/random.cc
        util/rate_limiter.cc
        util/ribbon_config.cc
        util/slice.cc
        util/file_checksum_helper.cc
        util/status.cc
        util/string_util.cc
        util/thread_local.cc
        util/threadpool_imp.cc
        util/xxhash.cc
        utilities/backupable/backupable_db.cc
        utilities/blob_db/blob_compaction_filter.cc
        utilities/blob_db/blob_db.cc
        utilities/blob_db/blob_db_impl.cc
        utilities/blob_db/blob_db_impl_filesnapshot.cc
        utilities/blob_db/blob_dump_tool.cc
        utilities/blob_db/blob_file.cc
        utilities/cache_dump_load.cc
        utilities/cache_dump_load_impl.cc
        utilities/cassandra/cassandra_compaction_filter.cc
        utilities/cassandra/format.cc
        utilities/cassandra/merge_operator.cc
        utilities/checkpoint/checkpoint_impl.cc
        utilities/compaction_filters.cc
        utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
        utilities/counted_fs.cc
        utilities/debug.cc
        utilities/env_mirror.cc
        utilities/env_timed.cc
        utilities/fault_injection_env.cc
        utilities/fault_injection_fs.cc
        utilities/fault_injection_secondary_cache.cc
        utilities/leveldb_options/leveldb_options.cc
        utilities/memory/memory_util.cc
        utilities/merge_operators.cc
        utilities/merge_operators/bytesxor.cc
        utilities/merge_operators/max.cc
        utilities/merge_operators/put.cc
        utilities/merge_operators/sortlist.cc
        utilities/merge_operators/string_append/stringappend.cc
        utilities/merge_operators/string_append/stringappend2.cc
        utilities/merge_operators/uint64add.cc
        utilities/object_registry.cc
        utilities/option_change_migration/option_change_migration.cc
        utilities/options/options_util.cc
        utilities/persistent_cache/block_cache_tier.cc
        utilities/persistent_cache/block_cache_tier_file.cc
        utilities/persistent_cache/block_cache_tier_metadata.cc
        utilities/persistent_cache/persistent_cache_tier.cc
        utilities/persistent_cache/volatile_tier_impl.cc
        utilities/simulator_cache/cache_simulator.cc
        utilities/simulator_cache/sim_cache.cc
        utilities/table_properties_collectors/compact_on_deletion_collector.cc
        utilities/trace/file_trace_reader_writer.cc
        utilities/trace/replayer_impl.cc
        utilities/transactions/lock/lock_manager.cc
        utilities/transactions/lock/point/point_lock_tracker.cc
        utilities/transactions/lock/point/point_lock_manager.cc
        utilities/transactions/lock/range/range_tree/range_tree_lock_manager.cc
        utilities/transactions/lock/range/range_tree/range_tree_lock_tracker.cc
        utilities/transactions/optimistic_transaction_db_impl.cc
        utilities/transactions/optimistic_transaction.cc
        utilities/transactions/pessimistic_transaction.cc
        utilities/transactions/pessimistic_transaction_db.cc
        utilities/transactions/snapshot_checker.cc
        utilities/transactions/transaction_base.cc
        utilities/transactions/transaction_db_mutex_impl.cc
        utilities/transactions/transaction_util.cc
        utilities/transactions/write_prepared_txn.cc
        utilities/transactions/write_prepared_txn_db.cc
        utilities/transactions/write_unprepared_txn.cc
        utilities/transactions/write_unprepared_txn_db.cc
        utilities/ttl/db_ttl_impl.cc
        utilities/wal_filter.cc
        utilities/write_batch_with_index/write_batch_with_index.cc
        utilities/write_batch_with_index/write_batch_with_index_internal.cc)

list(APPEND SOURCES
  utilities/transactions/lock/range/range_tree/lib/locktree/concurrent_tree.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/keyrange.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/lock_request.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/locktree.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/manager.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/range_buffer.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/treenode.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/txnid_set.cc
  utilities/transactions/lock/range/range_tree/lib/locktree/wfg.cc
  utilities/transactions/lock/range/range_tree/lib/standalone_port.cc
  utilities/transactions/lock/range/range_tree/lib/util/dbt.cc
  utilities/transactions/lock/range/range_tree/lib/util/memarena.cc)

message(STATUS "ROCKSDB_PLUGINS: ${ROCKSDB_PLUGINS}")
if ( ROCKSDB_PLUGINS )
  string(REPLACE " " ";" PLUGINS ${ROCKSDB_PLUGINS})
  foreach (plugin ${PLUGINS})
    add_subdirectory("plugin/${plugin}")
    foreach (src ${${plugin}_SOURCES})
      list(APPEND SOURCES plugin/${plugin}/${src})
      set_source_files_properties(
        plugin/${plugin}/${src}
        PROPERTIES COMPILE_FLAGS "${${plugin}_COMPILE_FLAGS}")
    endforeach()
    foreach (path ${${plugin}_INCLUDE_PATHS})
      include_directories(${path})
    endforeach()
    foreach (lib ${${plugin}_LIBS})
      list(APPEND THIRDPARTY_LIBS ${lib})
    endforeach()
    foreach (link_path ${${plugin}_LINK_PATHS})
      link_directories(AFTER ${link_path})
    endforeach()
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${${plugin}_CMAKE_SHARED_LINKER_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${${plugin}_CMAKE_EXE_LINKER_FLAGS}")
  endforeach()
endif()

if(HAVE_SSE42 AND NOT MSVC)
  set_source_files_properties(
    util/crc32c.cc
    PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul")
endif()

if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
  list(APPEND SOURCES
    util/crc32c_ppc.c
    util/crc32c_ppc_asm.S)
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")

if(HAS_ARMV8_CRC)
  list(APPEND SOURCES
    util/crc32c_arm64.cc)
endif(HAS_ARMV8_CRC)

if(WIN32)
  list(APPEND SOURCES
    port/win/io_win.cc
    port/win/env_win.cc
    port/win/env_default.cc
    port/win/port_win.cc
    port/win/win_logger.cc
    port/win/win_thread.cc)
if(WITH_XPRESS)
  list(APPEND SOURCES
    port/win/xpress_win.cc)
endif()

if(WITH_JEMALLOC)
  list(APPEND SOURCES
    port/win/win_jemalloc.cc)
endif()

else()
  list(APPEND SOURCES
    port/port_posix.cc
    env/env_posix.cc
    env/fs_posix.cc
    env/io_posix.cc)
endif()

if(WITH_FOLLY_DISTRIBUTED_MUTEX)
  list(APPEND SOURCES
    third-party/folly/folly/detail/Futex.cpp
    third-party/folly/folly/synchronization/AtomicNotification.cpp
    third-party/folly/folly/synchronization/DistributedMutex.cpp
    third-party/folly/folly/synchronization/ParkingLot.cpp
    third-party/folly/folly/synchronization/WaitOptions.cpp)
endif()

set(ROCKSDB_STATIC_LIB rocksdb${ARTIFACT_SUFFIX})
set(ROCKSDB_SHARED_LIB rocksdb-shared${ARTIFACT_SUFFIX})

option(ROCKSDB_BUILD_SHARED "Build shared versions of the RocksDB libraries" ON)


if(WIN32)
  set(SYSTEM_LIBS ${SYSTEM_LIBS} shlwapi.lib rpcrt4.lib)
else()
  set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT})
endif()

add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES} ${BUILD_VERSION_CC})
target_link_libraries(${ROCKSDB_STATIC_LIB} PRIVATE
  ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})

if(ROCKSDB_BUILD_SHARED)
  add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES} ${BUILD_VERSION_CC})
  target_link_libraries(${ROCKSDB_SHARED_LIB} PRIVATE
    ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})

  if(WIN32)
    set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES
      COMPILE_DEFINITIONS "ROCKSDB_DLL;ROCKSDB_LIBRARY_EXPORTS")
    if(MSVC)
      set_target_properties(${ROCKSDB_STATIC_LIB} PROPERTIES
        COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/${ROCKSDB_STATIC_LIB}.pdb")
      set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES
        COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/${ROCKSDB_SHARED_LIB}.pdb")
    endif()
  else()
    set_target_properties(${ROCKSDB_SHARED_LIB} PROPERTIES
                          LINKER_LANGUAGE CXX
                          VERSION ${rocksdb_VERSION}
                          SOVERSION ${rocksdb_VERSION_MAJOR}
                          OUTPUT_NAME "rocksdb${ARTIFACT_SUFFIX}")
  endif()
endif()

if(ROCKSDB_BUILD_SHARED AND NOT WIN32)
  set(ROCKSDB_LIB ${ROCKSDB_SHARED_LIB})
else()
  set(ROCKSDB_LIB ${ROCKSDB_STATIC_LIB})
endif()

option(WITH_JNI "build with JNI" OFF)
# Tests are excluded from Release builds
CMAKE_DEPENDENT_OPTION(WITH_TESTS "build with tests" ON
  "CMAKE_BUILD_TYPE STREQUAL Debug" OFF)
option(WITH_BENCHMARK_TOOLS "build with benchmarks" ON)
option(WITH_CORE_TOOLS "build with ldb and sst_dump" ON)
option(WITH_TOOLS "build with tools" ON)

if(WITH_TESTS OR WITH_BENCHMARK_TOOLS OR WITH_TOOLS OR WITH_JNI OR JNI)
  include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.8.1/fused-src)
endif()
if(WITH_JNI OR JNI)
  message(STATUS "JNI library is enabled")
  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/java)
else()
  message(STATUS "JNI library is disabled")
endif()

# Installation and packaging
if(WIN32)
  option(ROCKSDB_INSTALL_ON_WINDOWS "Enable install target on Windows" OFF)
endif()
if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
  if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
      # Change default installation prefix on Linux to /usr
      set(CMAKE_INSTALL_PREFIX /usr CACHE PATH "Install path prefix, prepended onto install directories." FORCE)
    endif()
  endif()

  include(GNUInstallDirs)
  include(CMakePackageConfigHelpers)

  set(package_config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/rocksdb)

  configure_package_config_file(
    ${CMAKE_CURRENT_LIST_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
    INSTALL_DESTINATION ${package_config_destination}
  )

  write_basic_package_version_file(
    RocksDBConfigVersion.cmake
    VERSION ${rocksdb_VERSION}
    COMPATIBILITY SameMajorVersion
  )

  install(DIRECTORY include/rocksdb COMPONENT devel DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")

  install(DIRECTORY "${PROJECT_SOURCE_DIR}/cmake/modules" COMPONENT devel DESTINATION ${package_config_destination})

  install(
    TARGETS ${ROCKSDB_STATIC_LIB}
    EXPORT RocksDBTargets
    COMPONENT devel
    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
  )

  if(ROCKSDB_BUILD_SHARED)
    install(
      TARGETS ${ROCKSDB_SHARED_LIB}
      EXPORT RocksDBTargets
      COMPONENT runtime
      ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
      RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
      LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
      INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
  endif()

  install(
    EXPORT RocksDBTargets
    COMPONENT devel
    DESTINATION ${package_config_destination}
    NAMESPACE RocksDB::
  )

  install(
    FILES
    ${CMAKE_CURRENT_BINARY_DIR}/RocksDBConfig.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/RocksDBConfigVersion.cmake
    COMPONENT devel
    DESTINATION ${package_config_destination}
  )
endif()

option(WITH_ALL_TESTS "Build all test, rather than a small subset" ON)

if(WITH_TESTS OR WITH_BENCHMARK_TOOLS)
  add_subdirectory(third-party/gtest-1.8.1/fused-src/gtest)
  add_library(testharness STATIC
  test_util/mock_time_env.cc
  test_util/testharness.cc)
  target_link_libraries(testharness gtest)
endif()

if(WITH_TESTS)
  set(TESTS
        db/db_basic_test.cc
        env/env_basic_test.cc
  )
  if(WITH_ALL_TESTS)
    list(APPEND TESTS
        cache/cache_reservation_manager_test.cc
        cache/cache_test.cc
        cache/lru_cache_test.cc
        db/blob/blob_counting_iterator_test.cc
        db/blob/blob_file_addition_test.cc
        db/blob/blob_file_builder_test.cc
        db/blob/blob_file_cache_test.cc
        db/blob/blob_file_garbage_test.cc
        db/blob/blob_file_reader_test.cc
        db/blob/blob_garbage_meter_test.cc
        db/blob/db_blob_basic_test.cc
        db/blob/db_blob_compaction_test.cc
        db/blob/db_blob_corruption_test.cc
        db/blob/db_blob_index_test.cc
        db/column_family_test.cc
        db/compact_files_test.cc
        db/compaction/clipping_iterator_test.cc
        db/compaction/compaction_job_stats_test.cc
        db/compaction/compaction_job_test.cc
        db/compaction/compaction_iterator_test.cc
        db/compaction/compaction_picker_test.cc
        db/compaction/compaction_service_test.cc
        db/comparator_db_test.cc
        db/corruption_test.cc
        db/cuckoo_table_db_test.cc
        db/db_with_timestamp_basic_test.cc
        db/db_block_cache_test.cc
        db/db_bloom_filter_test.cc
        db/db_compaction_filter_test.cc
        db/db_compaction_test.cc
        db/db_dynamic_level_test.cc
        db/db_flush_test.cc
        db/db_inplace_update_test.cc
        db/db_io_failure_test.cc
        db/db_iter_test.cc
        db/db_iter_stress_test.cc
        db/db_iterator_test.cc
        db/db_kv_checksum_test.cc
        db/db_log_iter_test.cc
        db/db_memtable_test.cc
        db/db_merge_operator_test.cc
        db/db_merge_operand_test.cc
        db/db_options_test.cc
        db/db_properties_test.cc
        db/db_range_del_test.cc
        db/db_secondary_test.cc
        db/db_sst_test.cc
        db/db_statistics_test.cc
        db/db_table_properties_test.cc
        db/db_tailing_iter_test.cc
        db/db_test.cc
        db/db_test2.cc
        db/db_logical_block_size_cache_test.cc
        db/db_universal_compaction_test.cc
        db/db_wal_test.cc
        db/db_with_timestamp_compaction_test.cc
        db/db_write_test.cc
        db/dbformat_test.cc
        db/deletefile_test.cc
        db/error_handler_fs_test.cc
        db/obsolete_files_test.cc
        db/external_sst_file_basic_test.cc
        db/external_sst_file_test.cc
        db/fault_injection_test.cc
        db/file_indexer_test.cc
        db/filename_test.cc
        db/flush_job_test.cc
        db/listener_test.cc
        db/log_test.cc
        db/manual_compaction_test.cc
        db/memtable_list_test.cc
        db/merge_helper_test.cc
        db/merge_test.cc
        db/options_file_test.cc
        db/perf_context_test.cc
        db/periodic_work_scheduler_test.cc
        db/plain_table_db_test.cc
        db/prefix_test.cc
        db/range_del_aggregator_test.cc
        db/range_tombstone_fragmenter_test.cc
        db/repair_test.cc
        db/table_properties_collector_test.cc
        db/version_builder_test.cc
        db/version_edit_test.cc
        db/version_set_test.cc
        db/wal_manager_test.cc
        db/wal_edit_test.cc
        db/write_batch_test.cc
        db/write_callback_test.cc
        db/write_controller_test.cc
        env/env_test.cc
        env/io_posix_test.cc
        env/mock_env_test.cc
        file/delete_scheduler_test.cc
        file/prefetch_test.cc
        file/random_access_file_reader_test.cc
        logging/auto_roll_logger_test.cc
        logging/env_logger_test.cc
        logging/event_logger_test.cc
        memory/arena_test.cc
        memory/memory_allocator_test.cc
        memtable/inlineskiplist_test.cc
        memtable/skiplist_test.cc
        memtable/write_buffer_manager_test.cc
        monitoring/histogram_test.cc
        monitoring/iostats_context_test.cc
        monitoring/statistics_test.cc
        monitoring/stats_history_test.cc
        options/configurable_test.cc
        options/customizable_test.cc
        options/options_settable_test.cc
        options/options_test.cc
        table/block_based/block_based_filter_block_test.cc
        table/block_based/block_based_table_reader_test.cc
        table/block_based/block_test.cc
        table/block_based/data_block_hash_index_test.cc
        table/block_based/full_filter_block_test.cc
        table/block_based/partitioned_filter_block_test.cc
        table/cleanable_test.cc
        table/cuckoo/cuckoo_table_builder_test.cc
        table/cuckoo/cuckoo_table_reader_test.cc
        table/merger_test.cc
        table/sst_file_reader_test.cc
        table/table_test.cc
        table/block_fetcher_test.cc
        test_util/testutil_test.cc
        trace_replay/block_cache_tracer_test.cc
        trace_replay/io_tracer_test.cc
        tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
        tools/io_tracer_parser_test.cc
        tools/ldb_cmd_test.cc
        tools/reduce_levels_test.cc
        tools/sst_dump_test.cc
        tools/trace_analyzer_test.cc
        util/autovector_test.cc
        util/bloom_test.cc
        util/coding_test.cc
        util/crc32c_test.cc
        util/defer_test.cc
        util/dynamic_bloom_test.cc
        util/file_reader_writer_test.cc
        util/filelock_test.cc
        util/hash_test.cc
        util/heap_test.cc
        util/random_test.cc
        util/rate_limiter_test.cc
        util/repeatable_thread_test.cc
        util/ribbon_test.cc
        util/slice_test.cc
        util/slice_transform_test.cc
        util/timer_queue_test.cc
        util/timer_test.cc
        util/thread_list_test.cc
        util/thread_local_test.cc
        util/work_queue_test.cc
        utilities/backupable/backupable_db_test.cc
        utilities/blob_db/blob_db_test.cc
        utilities/cassandra/cassandra_functional_test.cc
        utilities/cassandra/cassandra_format_test.cc
        utilities/cassandra/cassandra_row_merge_test.cc
        utilities/cassandra/cassandra_serialize_test.cc
        utilities/checkpoint/checkpoint_test.cc
        utilities/memory/memory_test.cc
        utilities/merge_operators/string_append/stringappend_test.cc
        utilities/object_registry_test.cc
        utilities/option_change_migration/option_change_migration_test.cc
        utilities/options/options_util_test.cc
        utilities/persistent_cache/hash_table_test.cc
        utilities/persistent_cache/persistent_cache_test.cc
        utilities/simulator_cache/cache_simulator_test.cc
        utilities/simulator_cache/sim_cache_test.cc
        utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
        utilities/transactions/optimistic_transaction_test.cc
        utilities/transactions/transaction_test.cc
        utilities/transactions/lock/point/point_lock_manager_test.cc
        utilities/transactions/write_prepared_transaction_test.cc
        utilities/transactions/write_unprepared_transaction_test.cc
        utilities/transactions/lock/range/range_locking_test.cc
        utilities/ttl/ttl_test.cc
        utilities/write_batch_with_index/write_batch_with_index_test.cc
    )
  endif()

  if(WITH_FOLLY_DISTRIBUTED_MUTEX)
    list(APPEND TESTS third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp)
  endif()

  set(TESTUTIL_SOURCE
      db/db_test_util.cc
      monitoring/thread_status_updater_debug.cc
      table/mock_table.cc
      utilities/cassandra/test_utils.cc
  )
  enable_testing()
  add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
  set(TESTUTILLIB testutillib${ARTIFACT_SUFFIX})
  add_library(${TESTUTILLIB} STATIC ${TESTUTIL_SOURCE})
  target_link_libraries(${TESTUTILLIB} ${ROCKSDB_LIB})
  if(MSVC)
    set_target_properties(${TESTUTILLIB} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/testutillib${ARTIFACT_SUFFIX}.pdb")
  endif()
  set_target_properties(${TESTUTILLIB}
        PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
        EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
        EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
  )

  foreach(sourcefile ${TESTS})
      get_filename_component(exename ${sourcefile} NAME_WE)
      add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile})
      set_target_properties(${exename}${ARTIFACT_SUFFIX}
        PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
        EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
        EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
        OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX}
      )
      target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${THIRDPARTY_LIBS} ${ROCKSDB_LIB})
      if(NOT "${exename}" MATCHES "db_sanity_test")
        gtest_discover_tests(${exename} DISCOVERY_TIMEOUT 120)
        add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
      endif()
  endforeach(sourcefile ${TESTS})

  if(WIN32)
    # C executables must link to a shared object
    if(ROCKSDB_BUILD_SHARED)
      set(ROCKSDB_LIB_FOR_C ${ROCKSDB_SHARED_LIB})
    else()
      set(ROCKSDB_LIB_FOR_C OFF)
    endif()
  else()
    set(ROCKSDB_LIB_FOR_C ${ROCKSDB_LIB})
  endif()

  if(ROCKSDB_LIB_FOR_C)
    set(C_TESTS db/c_test.c)
    add_executable(c_test db/c_test.c)
    target_link_libraries(c_test ${ROCKSDB_LIB_FOR_C} testharness)
    add_test(NAME c_test COMMAND c_test${ARTIFACT_SUFFIX})
    add_dependencies(check c_test)
  endif()
endif()

if(WITH_BENCHMARK_TOOLS)
  add_executable(db_bench${ARTIFACT_SUFFIX}
    tools/simulated_hybrid_file_system.cc
    tools/db_bench.cc
    tools/db_bench_tool.cc)
  target_link_libraries(db_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} ${THIRDPARTY_LIBS})

  add_executable(cache_bench${ARTIFACT_SUFFIX}
    cache/cache_bench.cc
    cache/cache_bench_tool.cc)
  target_link_libraries(cache_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} ${GFLAGS_LIB})

  add_executable(memtablerep_bench${ARTIFACT_SUFFIX}
    memtable/memtablerep_bench.cc)
  target_link_libraries(memtablerep_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} ${GFLAGS_LIB})

  add_executable(range_del_aggregator_bench${ARTIFACT_SUFFIX}
    db/range_del_aggregator_bench.cc)
  target_link_libraries(range_del_aggregator_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} ${GFLAGS_LIB})

  add_executable(table_reader_bench${ARTIFACT_SUFFIX}
    table/table_reader_bench.cc)
  target_link_libraries(table_reader_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} testharness ${GFLAGS_LIB})

  add_executable(filter_bench${ARTIFACT_SUFFIX}
    util/filter_bench.cc)
  target_link_libraries(filter_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} ${GFLAGS_LIB})

  add_executable(hash_table_bench${ARTIFACT_SUFFIX}
    utilities/persistent_cache/hash_table_bench.cc)
  target_link_libraries(hash_table_bench${ARTIFACT_SUFFIX}
    ${ROCKSDB_LIB} ${GFLAGS_LIB})
endif()

if(WITH_CORE_TOOLS OR WITH_TOOLS)
  add_subdirectory(tools)
  add_custom_target(core_tools
    DEPENDS ${core_tool_deps})
endif()

if(WITH_TOOLS)
  add_subdirectory(db_stress_tool)
  add_custom_target(tools
    DEPENDS ${tool_deps})
endif()

option(WITH_EXAMPLES "build with examples" OFF)
if(WITH_EXAMPLES)
  add_subdirectory(examples)
endif()

option(WITH_BENCHMARK "build benchmark tests" OFF)
if(WITH_BENCHMARK)
  add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
endif()