CMake系统学习笔记

发布于:2025-06-08 ⋅ 阅读:(17) ⋅ 点赞:(0)

CMake系统学习笔记

基础操作

最基本的案例

// code
#include <iostream>

int main()
{
    std::cout << "hello world " << std::endl;
    return 0;
}

// CMakeLists.txt
cmake_minimum_required(VERSION 3.0)

# 定义当前工程名称
project(demo)

add_executable(test main.cpp)
  
// 命令行编译执行  在Windows平台 build默认会生成visual studio项目工程
cmake -S . -B build 
// 编译方式1  build目录下生成test可执行文件
cd build && make
// 编译方式2  32个线程编译
cmake --build build -j32
  
// 利用cmake生成xcode工程项目
cmake -S . -B xcode -G "Xcode"

分步编译演示

# mac 平台分步编译命令
cmake --build build  --target help
The following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... edit_cache
... rebuild_cache
... test
... main.o
... main.i
... main.s

# 预处理
cmake --build build  --target main.i
# 汇编
cmake --build build  --target main.s
# 链接
cmake --build build  --target main.o
# 清理
cmake --build build  --target clean

# windows平台需要用namke
# 利用vs的控制台生成
cmake -S . -B nmake -G "NMake Makefiles"

多行注释

# 多行注释
#[[ 
注释内容
message(arg1 arg2 arg3)
cmake -S . -B build >log 2>&1
]]

message

# message基础使用
message("参数1")  #测试message
message("参数p1" "参数p2"  #[[注释在message中]] "p3" 123 测试)

# message高级使用-指定日志级别
# FATAL_ERROR 进程退出,生成退出 打印代码路径和行号 stderr
message(FATAL_ERROR "TEST FATAL_ERROR")

# SEND_ERROR 进程继续,生成退出 不会生成 add_executable add_library stderr
# 打印错误代码路径和行号 
message(SEND_ERROR "TEST SEND_ERROR")

# WARNING 打印代码路径和行号 stderr
message(WARNING "TEST WARNING")

#NOTICE等同于 none也就是不加  message("TEST NOTICE")  stderr
message("TEST none")
message(NOTICE "TEST NOTICE")

#STATUS 打印信息加前缀 --  用户可能感兴趣 stdout
message(STATUS "TEST STATUS")

#VERBOSE 加前缀 -- 默认不显示 用户需要的详细信息 stdout
message(VERBOSE "TEST VERBOSE")
# 设置日志显示级别 显示VERBOSE 信息
cmake -S . -B build --log-level=VERBOSE

# 标准输出重定向到文件log.txt 
cmake -S . -B build --log-level=VERBOSE > log.txt 

# 标准错误输出重定向到标准输出 
cmake -S . -B build --log-level=VERBOSE > log.txt  2>&1

#DEBUG 加前缀 -- 
message(DEBUG  "test DEBUG")
cmake -S . -B build --log-level=DEBUG

#TRACE 加前缀 -- 
message(TRACE "test TRACE")
cmake -S . -B build --log-level=TRACE


# message Reporting checks查找库日志
#[[
CHECK_START 开始记录将要执行检查的消息
CHECK_PASS 记录检查的成功结果
CHECK_FAIL 记录不成功的检查结果
]]
#开始查找
message(CHECK_START "查找xcpp")
# 查找库xcpp的代码
# message消息缩进
set(CMAKE_MESSAGE_INDENT "--")

#嵌套查找
message(CHECK_START "查找xlog")
#查找xlog代码
message(CHECK_PASS "成功")

message(CHECK_START "查找xthreadpool")
message(CHECK_FAIL "失败")

#取消缩进
set(CMAKE_MESSAGE_INDENT "")
#结束查找 查找失败
message(CHECK_FAIL "失败")
# message 显示颜色

#[[
\033[1;31;40m    <!--1-高亮显示 31-前景色红色  40-背景色黑色-->
\033[0m          <!--采用终端默认设置,即取消颜色设置-->
显示方式  
0                终端默认设置
1                高亮显示
4                使用下划线
5                闪烁
7                反白显示
8                不可见

前景色            背景色           颜色
---------------------------------------
30                40              黑色
31                41              红色
32                42              绿色
33                43              黃色
34                44              蓝色
35                45              紫红色
36                46              青蓝色
37                47              白色
]]

string(ASCII 27 Esc)

# Esc[0;31m
set(R "${Esc}[0;31m")   #红色
#Esc[0m 
set(E "${Esc}[m" )      #结束颜色设置
set(B "${Esc}[1;34m")   #蓝色高亮
set(RB "${Esc}[1;31;40m") #红色字体黑色背景

message("${R}红色内容${E} 默认颜色")
message("${B}蓝色内容${E} 默认颜色")
message("${RB}红色字体黑色背景${E} 默认颜色")

变量

set(VAR1 "测试变量VAR1的值")
message("VAR1=" ${VAR1})
message("VAR1 in string ${VAR1}")
message("\${VAR1}=${VAR1}")

# 嵌套取值
set(VAR2 "VAR1")
message("VAR2=" ${VAR2})
message("VAR2=" ${${VAR2}})

# 销毁
unset(VAR1)
message("\${VAR1}=${VAR1}")

cmake文件包含


include("cmake/test_cmake.cmake")

调试打印指令

# 方法一 build加上 -v参数,可以看到生成的详细命令
cmake --build build  -v

# 方法二 cmake中开启
set(CMAKE_VERBOSE_MAKEFILE ON)

# 方法三  打印库的指令
# 不能打印继承的属性
# 打印 INCLUDE_DIRECTORIES、INTERFACE_INCLUDE_DIRECTORIES属性
cmake_print_properties(TARGETS test PROPERTIES
INCLUDE_DIRECTORIES
INTERFACE_INCLUDE_DIRECTORIES
)

条件控制以及循环

#[[
if(<常量>) #constant 常量
如果常量是1, ON, YES, TRUE,Y或非零数(包括浮点数),则为真True。
如果常量是0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, 空字符串,或以-NOTFOUND结尾则为假False
命名布尔常量不区分大小写

if(<string>)
带引号的字符串计算为 false,除非字符串的值是真正的常量之一

if(<variable>)
如果给定一个定义为非假常量的值的变量,则为真否则为 False,包括变量未定义时。
宏参数不是变量。环境变量if(ENV{some_var})总是会评估为假。
]]


if(1)
    message("1 is true")
endif()

if(OFF) #false
    message("OFF is true?")
elseif(NO) #false
    message("NO is true?")
else()
    message("OFF NO is false!")
endif()


#[[
逻辑运算符
if(NOT <condition>)
如果条件不为真,则为真。
if(<cond1> AND <cond2>)
如果两个条件都是真的,则为真。

if(<cond1> OR <cond2>)
如果任一条件是真的,则为真。

if((condition) AND (condition OR (condition)))
首先评估括号内的条件,然后评估其余条件。
]]

#案例
set(VAR_OFF OFF)
if(NOT VAR_OFF) #如果条件不为真,则为真。
    message("NOT VAR_OFF (true)")
endif()

if(TRUE AND ON)
    message("TRUE and ON is true")
endif()

if(TRUE OR OFF)
    message("TRUE OR OFF is true?")
else()
    message("TRUE OR OFF is false?")
endif()

缓存变量

#[[
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
	type
		BOOL
			ON/OFF 选择框
		FILEPATH
			文件选择
		PATH
			目录选择
		STRING
			字符串
		INTERNAL
			内部变量
	docstring
		变量说明
	FORCE 
		强制修改缓存,不设置第二次调用值不改
]]

#设置缓存变量 字符串类型
set(VAR1 "CACHE VAR1 VALUE1-1 " CACHE STRING  "cache doc")

# 强制修改缓存 FORCE
set(VAR1 "CACHE VAR1 VALUE1-3 FORCE" CACHE STRING "cache doc" FORCE)

# CACHE变量作用域是全局的
# 普通变量的作用域 自身和子模块

文件属性

#[[
set_property(<GLOBAL                      |
              DIRECTORY [<dir>]           |
              TARGET    [<target1> ...]   |
              SOURCE    [<src1> ...]
                        [DIRECTORY <dirs> ...]
                        [TARGET_DIRECTORY <targets> ...] |
              INSTALL   [<file1> ...]     |
              TEST      [<test1> ...]     |
              CACHE     [<entry1> ...]    >
             [APPEND] [APPEND_STRING]
             PROPERTY <name> [<value1> ...])

get_property(<variable>
             <GLOBAL             |
              DIRECTORY [<dir>]  |
              TARGET    <target> |
              SOURCE    <source>
                        [DIRECTORY <dir> | TARGET_DIRECTORY <target>] |
              INSTALL   <file>   |
              TEST      <test>   |
              CACHE     <entry>  |
              VARIABLE           >
             PROPERTY <name>
             [SET | DEFINED | BRIEF_DOCS | FULL_DOCS])

define_property(<GLOBAL | DIRECTORY | TARGET | SOURCE |
                 TEST | VARIABLE | CACHED_VARIABLE>
                 PROPERTY <name> [INHERITED]
                 [BRIEF_DOCS <brief-doc> [docs...] ]
                 [FULL_DOCS <full-doc> [docs...] ]
                 [INITIALIZE_FROM_VARIABLE <variable>])     

]]

#设置全局属性
set_property(GLOBAL PROPERTY TEST_GLOBAL "test global 001")
#获取全局属性 可以获取子目录中cmake定义的全局属性
get_property(val GLOBAL PROPERTY TEST_GLOBAL)
message("TEST_GLOBAL = ${val}")


#APPEND APPEND_STRING
# APPEND 数组方式添加 TEST_APPEND = append 001;append 002;append 003 
set_property(GLOBAL APPEND PROPERTY TEST_APPEND "append 001")
set_property(GLOBAL APPEND PROPERTY TEST_APPEND "append 002")
set_property(GLOBAL APPEND PROPERTY TEST_APPEND "append 003")
get_property(val GLOBAL PROPERTY TEST_APPEND)
message("TEST_APPEND = ${val}")

# APPEND_STRING 字符串拼接 append string 001 append string 002 append string 003
set_property(GLOBAL APPEND_STRING PROPERTY TEST_APPEND_STRING "append string 001 ")
set_property(GLOBAL APPEND_STRING PROPERTY TEST_APPEND_STRING "append string 002 ")
set_property(GLOBAL APPEND_STRING PROPERTY TEST_APPEND_STRING "append string 003 ")
get_property(val GLOBAL PROPERTY TEST_APPEND_STRING)
message("TEST_APPEND_STRING = ${val}")


set_property(GLOBAL PROPERTY P1 "p1")
get_property(var GLOBAL PROPERTY P1)
message("P1 SET = ${var}")
if(var)
    message("P1 is set")
else()
    message("P1 not set")
endif()


#只有调用define_property之后才会为1
get_property(var GLOBAL PROPERTY P1 DEFINED)
message("P1 DEFINED = ${var}")
if(NOT var)
    # 目前var是0
    message("P1 not defined")
endif()


#目录属性
set_property(DIRECTORY . PROPERTY DIR1 "dir001")
get_property(var DIRECTORY . PROPERTY DIR1)
message("DIR1 = ${var}")

#文件属性
set_property(SOURCE main.cpp PROPERTY S1 "s1 value")
get_property(var SOURCE main.cpp PROPERTY S1)
message("SOURCE S1 = ${var}")

# cmake传递变量给c++
# cmake 预置属性 COMPILE_DEFINITIONS    类似 传递预处理变量(宏变量) -DPARA1 1234
set_property(SOURCE main.cpp PROPERTY COMPILE_DEFINITIONS "PARA1=1234")

环境变量

#环境变量使用 全局无缓存变量
#自定义环境变量
set(ENV{MYENV} "test env value")
message("MYENV = $ENV{MYENV}")

#系统环境变量  命令行 env 回车查看系统环境变量
message("USER NAME = $ENV{USER}")
message("PATH = $ENV{PATH}")

string查找和字符串相关

# 取出begin 和end之间的内容test cmake string
set(STR1 "  begin test cmake string end  ")
# 查找的开头字符串
set(BSTR  "begin")
#在${STR1}中 查找${BSTR}的位置存入start
# cmake位置以0开始
string(FIND ${STR1} ${BSTR} startIndex)
message("FIND ${BSTR} POS ${startIndex}")

string(FIND ${STR1} "end" endIndex)
message("FIND end POS ${endIndex}")

#去掉begin字符位置
#获取字符串长度
string(LENGTH ${BSTR} size)
message("size = ${size}")
math(EXPR startIndex "${startIndex} + ${size}")
message("startIndex = ${startIndex}")

# endIndex等于开始到结束的长度
math(EXPR length "${endIndex} - ${startIndex}")
message("length = ${length}")

#获取字串 在${STR1字符串${startIndex}位置取${length}长度字符串写入substr
string(SUBSTRING ${STR1} ${startIndex} ${length} substr)
message("SUBSTRING substr = [${substr}]")
#去掉头尾 空格 \t \n \r
string(STRIP ${substr} substr)
message("STRIP substr = [${substr}]")
#转成大写 test cmake string
string(TOUPPER ${substr} substr)
message("TOUPPER substr = [${substr}]")

#字符串追加 TEST CMAKE STRING
string(APPEND substr " append01 " " append02 ")
message("APPEND substr = [${substr}]")

#字符串替换 STRINGappend01append02
# string(REPLACE <match-string> <replace-string> <out-var> <input>...)
string(REPLACE "append" "REPLACE" substr ${substr})
message("REPLACE substr = [${substr}]")
# string 操作json字符串

#测试用json
# json对象 {}  json 数组 [] 
# 格式 key:value
set( tjson
[=[
{
    "webs":{
        "web":[
        {
            "name":"cmake",
            "url":"cmake.org.cn"
        },{
            "name":"ffmpeg",
            "url":"ffmpeg.club"
        }
        ,{
            "name":"tt",
            "url":"tt.club"
        }
        ]
    }
}
]=]
)
message(${tjson})
#[[
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
       GET <json-string> <member|index> [<member|index> ...])
]]
# 访问webs->web[0]->name
string(JSON var ERROR_VARIABLE evar
        GET ${tjson} webs web 0 name
)

# 错误信息
if(evar)
    message("error = ", ${evar})
endif()
message("webs web 0 = ${var}") 

string(JSON var ERROR_VARIABLE evar
        GET ${tjson} webs web 1 url
    )
message("webs web 1 = ${var}") 


#读取json数组长度
#[[
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
       LENGTH <json-string> [<member|index> ...])
]]
string(JSON web_count ERROR_VARIABLE evar
    LENGTH ${tjson} webs web
)
message("JSON LENGTH = ${web_count}")


#json的增加和修改
#[[
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
       SET <json-string> <member|index> [<member|index> ...] <value>)
value必须是json
]]
#json的增加
string(JSON set_out SET ${tjson} webs web ${web_count} [=[{
    "name":"cpp",
    "url":"cppds.com"
}]=])

message("set_out = ${set_out}")
#json的修改 SET
string(JSON set_out2 SET ${set_out} webs web 1 [=[{
    "name":"ffmpeg2",
    "url":"ffmpeg.club"
}]=])
message("set_out2 = ${set_out2}")


#json的删除 REMOVE
#[[
string(JSON <out-var> [ERROR_VARIABLE <error-variable>]
       REMOVE <json-string> <member|index> [<member|index> ...])
]]
string(JSON remove_out REMOVE ${set_out2} webs web 0)
string(JSON remove_out REMOVE ${remove_out} webs web 1)
message("remove_out = ${remove_out}")

list相关操作

#list变量初始化
set(src "a" "b" "c" "d;e;f")
message("src = ${src}")
#获取list变量长度
list(LENGTH src length)
message("src length = ${length}")
#list追加写入
list(APPEND  src "g")
list(APPEND  src "h" "i")
message("src = ${src}")

# list访问 下标 0,1,2 。。。 结尾往前 -1 -2 -3 
# src = a;b;c;d;e;f;g;h;i
list(GET src 1 var)
message("GET src 1  = ${var}")
list(GET src 5 var)
message("GET src 5  = ${var}")
list(GET src -1 var)
message("GET src -1  = ${var}")
list(GET src -2 var)
message("GET src -2  = ${var}")

# 拼接所有的节点
list(JOIN src "|" var) # 输出 a|b|c|d|e|f|g|h|i
message("JOIN ${var}")
list(JOIN src "" var)
message("JOIN ${var}")

# 取其中一部分数组
list(SUBLIST src  0 3 var)
message("SUBLIST ${var}")

# 查找内容
# src = a;b;c;d;e;f;g;h;i
list(FIND src "d" b) # 3
message("FIND ${b}")

#插入节点
# src = a;b;c;d;e;f;g;h;i
list(INSERT src 4 "d1")
list(INSERT src 2 "b1")
list(INSERT src 2 "b")
message("INSERT src ${src}")

#通过节点值删除
list(REMOVE_ITEM src "b")
message("REMOVE src = ${src}")

#删除指定位置  e
list(REMOVE_AT src 5)
#重新插入 相当于修改 E
list(INSERT src 5 "E")
message("REMOVE_AT INSERT src = ${src}")

#双向队列操作方式
#后端出队 b1;c;d;d1;E;f;g;h
list(POP_BACK src var)
message("POP_BACK ${var}")
message("src = ${src}")

#前端出队 先进先出 b1;c;d;d1;E;f;g;h
list(POP_FRONT src var)
message("POP_FRONT ${var}")
message("src = ${src}")


#去掉重复元素
set(rsrc "a;b;c;d;d;b;a;z;t;f")
message("rsrc = ${rsrc}")
list(REMOVE_DUPLICATES rsrc)
message("REMOVE_DUPLICATES rsrc = ${rsrc}")

# 数据排序
list(SORT rsrc)
message("SORT rsrc = ${rsrc}")
set(arr "3;23;122;157;1;5;7")
#默认字符串排序
list(SORT arr)
message("arr = ${arr}")


#自然数排序
list(SORT arr COMPARE NATURAL)
message("NATURAL arr = ${arr}")

foreach/while使用

#[[
foreach(<loop_var> <items>)
  <commands>
endforeach()
]]
# 遍历范围 从0开始 到stop结束  0 1 2 3 4 5
# #foreach(<loop_var> RANGE <stop>)
set(out "")
foreach(var RANGE 5)  # 0 1 2 3 4 5
    message("var = ${var}")
    string(APPEND out ${var} " ")
endforeach()
message("out = ${out}")

# foreach(<loop_var> RANGE <start> <stop> [<step>])
foreach(var RANGE  1 3)
    message(${var})
endforeach()

set(out "")
# 遍历范围从 0 到50 每次加5
foreach(var RANGE 0 50 5) # 0 5 10 15 20 25 30 35 40 45 50
    string(APPEND out ${var} " ")
endforeach()
message("out = ${out}")

# foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])
# 遍历list数组
set(args "a" "b" "c" "d" "e")
foreach(var IN LISTS args)
    message(${var})
endforeach()

foreach(var IN ITEMS ${args}) # 必须是list值
    message(${var})
endforeach()

foreach(var IN ITEMS 1;2;3;4)
    message(${var})
endforeach()


#按次序遍历多个list 可实现list按条件拼接
set(A "0;1;2;3")
set(B "4;5;6;7")
set(out "")
foreach(var IN LISTS A B)
    string(APPEND out ${var} " ")
endforeach()
message("LISTS:${out}")


# 同步遍历多个list
# foreach(<loop_var>... IN ZIP_LISTS <lists>)
set(arr1 "1;2;3;4;5;6;7")
set(arr2 "A;B;C;D;E;F;G")
foreach(var IN ZIP_LISTS arr1 arr2)
    message("${var_0}:${var_1}")
endforeach()

foreach(var1 var2 IN ZIP_LISTS arr1 arr2)
    message("${var1} - ${var2}")
endforeach()

# break() continue()
foreach(var RANGE 100) # 0...100
    #取余为3显示数字,其他显示.
    math(EXPR re "${var} % 3") # 0是false 其他是true
    if(NOT re) #=0
        message(${var})
        continue()# 本次循环结束
    endif()
    #大于50退出
    if(var GREATER 50)
        break() #整个foreach循环结束
    endif()
    message(".")
endforeach()

#[[
while(<condition>)
  <commands>
endwhile()
]]
# 防止死循环
set(var 1)
while(var)
    math(EXPR var "${var}+1")
    math(EXPR re "${var} % 10 ")
    if(re) # !=0
        continue()
    endif()

    message(${var})
    if(var GREATER 100)
        set(var 0) # break();
    endif()
endwhile()

宏的使用

macro(my_macro)
    message("in my macro")
    set(RET 1)
endmacro()
#宏名称大小写不敏感,尽量只用小写
# 调用是将宏代码赋值过来
my_macro()
My_macro()
message("RET = ${RET}")


#固定参数,实参数量要等于或者超过形参
macro(foo arg1 arg2)
    message("arg1 = ${arg1} arg2=${arg2}")  
    if(arg1) # 宏参数不是变量
        message("== arg1 true ==")
    endif()  
    if(${arg1})
        message("arg1 true")
    endif()
endmacro()
foo(TRUE "test")


macro(foo2)
    #参数数量
    message("ARGC = ${ARGC}")

    #参数list
    message("ARGN = ${ARGN}")

    #ARGN不是变量只能取值使用
    foreach(var IN ITEMS ${ARGN})
        message("var = ${var}")
    endforeach()

    message("ARGV0 = ${ARGV0}")
    message("ARGV1 = ${ARGV1}")
    message("ARGV2 = ${ARGV2}")
endmacro()
foo2(1 "test" True)


macro(my_parse)
    message("ARGN = ${ARGN}")
    cmake_parse_arguments(
        "MY"  #前缀
        "LOG;FILE"  #option
        "BIN;LIB"  # 单值
        "TARGETS"  # 多值
        ${ARGN} #参数数组
    )
    message("MY_LOG = ${MY_LOG}")
    message("MY_FILE = ${MY_FILE}")
    message("MY_BIN = ${MY_BIN}")
    message("MY_TARGETS = ${MY_TARGETS}")
    #参数类型传递错误
    message("MY_UNPARSED_ARGUMENTS = ${MY_UNPARSED_ARGUMENTS}" )
    #未传值错误
    message("MY_KEYWORDS_MISSING_VALUES = ${MY_KEYWORDS_MISSING_VALUES}")
endmacro()

my_parse(
LOG "mylog"
BIN ../../bin 
TARGETS "tar1" "tar1" 
LIB
)

函数

function(fun1 arg1 arg2)
    if(arg1)
        message("arg1 is true")
        return()
    endif()
    message("IN function fun1 arg1 = ${arg1} arg2 = ${arg2}")
    if(arg2) #函数区别于宏可以当作变量
        message("arg2 = TRUE")
    endif()
    foreach(var IN LISTS ARGN) #遍历所有变参
        message("var = ${var}")
    endforeach()
endfunction()

fun1(OFF TRUE "testfunc1" 1 3 ON)
fun1(ON TRUE "testfunc1" 1 3 ON)


function(fun_var arg1)
    # arg1 参数遍历如果有全局遍历名字一样还是取局部的变量
    message("in fun_var ${arg1}")
    message("var1 = ${var1}")
    set(var1 "fun var") #不能修改外部变量 这一步调用相当于创建了一个局部变量
    set(RET "1" PARENT_SCOPE) # 设定变量作用域到父 (调用函数者、父目录)
endfunction()

set(var1 "main")
set(arg1 "main arg1")
fun_var(123)
message("var1 = ${var1}")
message("RET = ${RET}")

生成表达式

#[[
逻辑运算符
$<BOOL:string>  
                转换string为0或1。评估0以下任何一项是否为真:
                string是空的,
                string是不区分大小写的等于 0, FALSE, OFF, N, NO, IGNORE, or NOTFOUND, or
                string以后缀结尾-NOTFOUND(区分大小写)。
                否则计算为1。
$<NOT:condition> 取反 0变1 1变0
$<AND:conditions>
$<OR:conditions>

条件表达式
$<condition:true_string>  0返回空串 1 返回true_string
]]  # $<BOOL:OFF>  ==》 0   $<0:TEST1=123>
target_compile_definitions(cmake_exp PUBLIC "$<$<BOOL:OFF>:TEST1=123>")


#### 测试生成表达式的方式 ###############
### 利用cmake的错误来查看  
# $<AND:1,0>"  通过设置头文件目录错误信息查看表达式的值  
#target_include_directories(cmake_exp PUBLIC "$<AND:1,0>")
target_include_directories(cmake_exp PUBLIC "$<OR:0,1>")

set(LIB ON)
# LIB等于OFF时显示STATIC ON显示空
target_include_directories(cmake_exp PUBLIC "$<$<NOT:$<BOOL:${LIB}>>:STATIC>")
#在配置阶段不处理生成表达式
message($<$<NOT:$<BOOL:${LIB}>>:STATIC>)


# 字符串比较
# $<STREQUAL:string1,string2>
# $<EQUAL:value1,value2>
#target_include_directories(cmake_exp PUBLIC "$<STREQUAL:string1,string1>")
#target_include_directories(cmake_exp PUBLIC "$<EQUAL:123,1>")

# 变量查询
# $<CONFIG:cfgs>
# $<CONFIG> Debug Release 。。
#target_include_directories(cmake_exp PUBLIC "$<CONFIG>")
#$<CONFIG:Debug,Release>配置项式Debug,Release之一返回1
target_include_directories(cmake_exp PUBLIC "$<CONFIG:Debug,Release>")

target_include_directories包含目录详解

#[[
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
]]

#案例

cmake_minimum_required(VERSION 3.20)
project(cmake_target_include)

# WRITE 写文件 清空原数据,如果文件不存在则创建
file(WRITE a.cpp [=[ 
#include <iostream>
void A()
{
    std::cout<<"In A "<<std::endl;
}
]=]
)

# 静态库
add_library(A STATIC a.cpp)

# 打印结果
 #Properties for TARGET A:
 #  A.INCLUDE_DIRECTORIES = "/A_PUBLIC"
 # A.INTERFACE_INCLUDE_DIRECTORIES = "/A_PUBLIC"
 # PUBLIC  改变 INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES  依赖者和自己都引用
# target_include_directories(A PUBLIC "/A_PUBLIC")

# 打印结果
#Properties for TARGET A:
  # A.INCLUDE_DIRECTORIES = "/A_PUBLIC"
  # A.INTERFACE_INCLUDE_DIRECTORIES = <NOTFOUND>
 # PRIVATE  改变 INCLUDE_DIRECTORIES 只有自己用
# target_include_directories(A PRIVATE "/A_PUBLIC")

# 打印结果
#Properties for TARGET A:
  # A.INCLUDE_DIRECTORIES = <NOTFOUND>
  # A.INTERFACE_INCLUDE_DIRECTORIES = "/A_PUBLIC"
 # INTERFACE  改变 INTERFACE_INCLUDE_DIRECTORIES  只有依赖者引用
target_include_directories(A INTERFACE "/A_PUBLIC")

#打印输出属性
include(CMakePrintHelpers)
cmake_print_properties(TARGETS A PROPERTIES
INCLUDE_DIRECTORIES
INTERFACE_INCLUDE_DIRECTORIES
)

target_compile_definitions 设置编译宏

#设定A库的宏 代码中A_VAR = 123  INTERFACE PUBLIC PRIVATE 参考 target_include_directories
target_compile_definitions(A PUBLIC A_VAR=123)

# 导入依赖库配置
target_link_libraries() 
# 编译参数
target_compile_options()

动态库生成版本号和符号链接

cmake_minimum_required(VERSION 3.0)

# 定义当前工程名称
project(demo)

add_library(demo SHARED test.cpp)

set_target_properties(demo PROPERTIES
VERSION "2.0.1"
SOVERSION "15" # 对执行程序无效,只有动态库可以设置
NO_SONAME OFF #OFF生成 ON不生成库的符号链接
)

Debug/Release配置

#Linux mac 控制方法,vs项目不可用
#CMAKE_BUILD_TYPE
#linux默认为空,既不是debug也不是release
# set(CMAKE_BUILD_TYPE Debug)  cmake文件中设置debug模式

# 命令行设置模式
#cmake -S . -B build -D CMAKE_BUILD_TYPE=DEBUG 
#cmake -S . -B build -D CMAKE_BUILD_TYPE=Release    
#cmake -S . -B build -D CMAKE_BUILD_TYPE=RelWithDebInfo
#cmake -S . -B build -D CMAKE_BUILD_TYPE=MinSizeRel

# windows vs nmake
# vs在生成阶段无法控制配置(自动生成4种)
# cmake --build win --config Release
# cmake --build win --config RelWithDebInfo


cmake_minimum_required(VERSION 3.0)

# 定义当前工程名称
project(demo)

add_library(demo test.cpp)

set(OUT_LIB_PATH ${CMAKE_SOURCE_DIR}/lib)
set(OUT_EXE_PATH ${CMAKE_SOURCE_DIR}/bin) # 执行程序和dll


set_target_properties(demo PROPERTIES
# 静态库和lib文件文件的输出 
ARCHIVE_OUTPUT_DIRECTORY ${OUT_LIB_PATH}
ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${OUT_LIB_PATH}/debug
ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${OUT_LIB_PATH}/release
)

add_library(dlib SHARED test.cpp test.h)

target_include_directories(dlib PUBLIC include)

#动态库属性
set_target_properties(dlib PROPERTIES
# windows lib文件文件的输出 
ARCHIVE_OUTPUT_DIRECTORY ${OUT_LIB_PATH}
ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${OUT_LIB_PATH}/debug
ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${OUT_LIB_PATH}/release

# windos dll文件输出路径
RUNTIME_OUTPUT_DIRECTORY ${OUT_EXE_PATH}
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${OUT_EXE_PATH}/debug
RUNTIME_OUTPUT_DIRECTORY_RELEASE ${OUT_EXE_PATH}/release

#linux .so 和mac
LIBRARY_OUTPUT_DIRECTORY ${OUT_LIB_PATH}
LIBRARY_OUTPUT_DIRECTORY_DEBUG ${OUT_LIB_PATH}/debug
LIBRARY_OUTPUT_DIRECTORY_RELEASE ${OUT_LIB_PATH}/release

#windows pdb调试文件
PDB_OUTPUT_DIRECTORY ${OUT_LIB_PATH}/pdb
PDB_OUTPUT_DIRECTORY_DEBUG ${OUT_LIB_PATH}/pdb

#debug 版本加后缀
DEBUG_POSTFIX "d"
)


add_executable(main main.cpp)
target_link_libraries(main)

set_target_properties(main PROPERTIES
# windos linux执行文件输出路径
RUNTIME_OUTPUT_DIRECTORY ${OUT_EXE_PATH}
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${OUT_EXE_PATH}/debug
RUNTIME_OUTPUT_DIRECTORY_RELEASE ${OUT_EXE_PATH}/release


#调试路径 工作目录
#VS_DEBUGGER_WORKING_DIRECTORY  ${OUT_EXE_PATH}
# $<CONFIG:Debug> 0 1 
#$<IF:1,debug,release> 满足条件 返回debug
#$<IF:0,debug,release> 不满足条件 返回release
VS_DEBUGGER_WORKING_DIRECTORY $<IF:$<CONFIG:Debug>,debug,release>
)

if(MSVC)
set_target_properties(main PROPERTIES
#debug 版本加后缀
DEBUG_POSTFIX "d"
)
endif()

install 安装

### 安装目标 DESTINATION指定相对 CMAKE_INSTALL_PREFIX 的输出路径
## 默认安装路径 
#    linux /usr/local  
#    windows    C:/Program Files (x86)/
# Linux
#    cmake -S . -B build -DCMAKE_INSTALL_PREFIX=./out
# Windows 默认安装 Release版本
# cmake -S . -B win -DCMAKE_INSTALL_PREFIX=win_out
# cmake --build win
# cmake --install win --config Debug
# cmake --build win --config Release
# cmake --install win
install(TARGETS slib dlib ${PROJECT_NAME} DESTINATION bin)

#[[
目标分类输出
	RUNTIME
		由add_executable创建执行程序
		windows动态链接库dll文件
	ARCHIVE
		windows动态库库导出符号 .lib
		静态库
			add_library添加STATIC 参数
			windows是 .lib, Unix、Linux和MinGW是.a
	LIBRARY
		动态库
			add_library 使用SHARED 参数 
				linux、unix
					.so
				mac
					dylib
	PUBLIC_HEADER、PRIVATE_HEADER
]]

install(TARGETS ${PROJECT_NAME}
	RUNTIME DESTINATION test_install/bin  # 执行程序和dll文件输出
)

install(TARGETS slib dlib
  RUNTIME DESTINATION test_install/bin  # 执行程序和dll文件输出
  ARCHIVE DESTINATION test_install/lib  # 静态库和windows动态库导出符号 .lib
  LIBRARY DESTINATION test_install/lib  # linux和mac的动态库 .so .dylib

  #头文件的安装
  PUBLIC_HEADER DESTINATION test_install/include #公开头文件
  PRIVATE_HEADER DESTINATION test_install/inc    #内部头文件
)


#[[
debug release 不同输出路径
    windows 编译过程
        cmake -S . -B win -DCMAKE_INSTALL_PREFIX=win_out
        cmake --build win --config Debug
        cmake --install win --config Debug
    Linux 编译过程  Debug, Release, RelWithDebInfo and MinSizeRel,
        cmake -S . -B build -DCMAKE_INSTALL_PREFIX=out -DCMAKE_BUILD_TYPE=Debug
        cmake --build build
        cmake --install build --config Debug


        cmake -S . -B build -DCMAKE_INSTALL_PREFIX=out -DCMAKE_BUILD_TYPE=Release
        cmake --build build
        cmake --install build --config Release
]]
install(TARGETS ${PROJECT_NAME}
CONFIGURATIONS Debug
RUNTIME DESTINATION debug/bin
)

install(TARGETS ${PROJECT_NAME}
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
RUNTIME DESTINATION release/bin
)
# 1、install  文件类型安装和权限
cmake_minimum_required(VERSION 3.20)
project(install_file)

file(WRITE a.h "")
file(WRITE b.h "")
file(WRITE c.h "")

# 文件安装到指定目录
install(FILES a.h b.h DESTINATION include)

# 目标可选 OPTIONAL 目标不存在不出错
install(FILES d.h DESTINATION inc OPTIONAL)

# 文件类型 TYPE DOC LIB INCLUDE
include(GNUInstallDirs)
message("CMAKE_INSTALL_DATAROOTDIR = ${CMAKE_INSTALL_DATAROOTDIR}")
install(FILES a.h TYPE DOC)     # <DATAROOT dir>/doc
install(FILES b.h TYPE LIB)     # lib
install(FILES c.h TYPE INCLUDE) # include


# 文件权限 windows目录无效
# 默认 权限 OWNER_WRITE, OWNER_READ, GROUP_READ, WORLD_READ
install(FILES a.h DESTINATION pub
PERMISSIONS 
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_WRITE GROUP_EXECUTE
WORLD_READ WORLD_WRITE WORLD_EXECUTE
)


# 2、过滤git和指定后缀文件
cmake_minimum_required(VERSION 3.22)
project(install_dir)
file(WRITE doc/index.html "")
file(WRITE doc/doc.html "")
file(WRITE doc/doc2.htm "")
file(WRITE doc/doc.cc "")
file(WRITE doc/doc.c "")
file(WRITE doc/sub/doc.html "")
file(WRITE doc/include/doc.h "")
file(WRITE doc/.svn/config "")
file(WRITE doc/.git/config "")
# DOC类型指定安装路径  share/doc
# 安装doc目录下所有文件,包含子目录中,空子目录也创建
install(DIRECTORY doc TYPE DOC)

install(DIRECTORY doc DESTINATION doc2)

# 过滤只复制*.html *.htm文件 包含子目录
install(DIRECTORY doc DESTINATION html_doc
FILES_MATCHING
PATTERN "*.html"
PATTERN "*.htm"
)

# 排除 .git 和.svn目录
install(DIRECTORY doc DESTINATION no_git_doc
PATTERN ".git" EXCLUDE
PATTERN ".svn" EXCLUDE
)


# 排除 .git 和.svn目录
install(DIRECTORY doc DESTINATION src
FILES_MATCHING
PATTERN "*.cc"
PATTERN "*.c"
PATTERN ".git" EXCLUDE
PATTERN ".svn" EXCLUDE
)


# 3、install期间执行代码
project(install_code)
FILE(WRITE a.h "")
FILE(WRITE b.h "")
install(CODE "message(\"begin install\")")

install(FILES a.h TYPE INCLUDE)
install(CODE "message(\"a.h install success!\")")
install(FILES b.h TYPE INCLUDE)
install(CODE "message(\"b.h install success!\")")

# 写入安装的时间
# string(TIMESTAMP now "%Y-%m-%d %H:%M:%S")
# 获取当前时间戳,并转换时间格式,写入now变量
install (CODE [=[

string(TIMESTAMP now "%Y-%m-%d %H:%M:%S") 
message(${now})
FILE(APPEND install_log.txt "${now}\n")

]=])


# 4、分组安装
project(cmake_component)
FILE(WRITE "a.cpp" "")
FILE(WRITE "doc.html" "")

install(FILES a.cpp 
DESTINATION src COMPONENT src
)

install(FILES doc.html 
DESTINATION doc COMPONENT doc
)

# 执行过程
#  cmake -S . -B build -DCMAKE_INSTALL_PREFIX=out
#  cd build
#  cmake -DCOMPONENT=src -P cmake_install.cmake
# -- Install component: "doc"
#  cmake -DCOMPONENT=doc -P cmake_install.cmake

交叉编译

# toolchain.camke
# 工具链里面一般需要配置的数据
# CMAKE_SYSTEM_NAME 必填,系统名称:Linux、 Windows、
# CMAKE_SYSTEM_PROCESSOR 可选,处理器或者硬件名称
# CMAKE_C_COMPILER c编译器全路径
# CMAKE_CXX_COMPILER C++编译器全路径
# CMAKE_SYSROOT 系统头文件路径  可选

# CMAKE_TOOLCHAIN_FILE 指定工具链路径

# 编译命令
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=name_toolchain.camke
# linux  arm  toolchain.camke  案列
# cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=linux_arm_toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)

# 安装工具链
# tar -xvf gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz
# /home/xcj/code/tools/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin
set(tools /home/xcj/code/tools/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu)
set(CMAKE_C_COMPILER ${tools}/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/aarch64-linux-gnu-g++)
# Android  ndk 编译案例

SDK路径
# mac  /Users/san/Library/Android/sdk
# windows D:\Android\Sdk
# ANDROID_ABI  x86 x86_64 armeabi-v7a arm64-v8a

CMAKE_TOOLCHAIN_FILE
# mac /Users/30san/Library/Android/sdk/ndk/24.0.8215888/build/cmake/android.toolchain.cmake
# windows C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake

ANDROID_NDK   C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529
ANDROID_PLATFORM  android-30 

# windows 进入vs自带控制台开发环境
cmake -S . -B build -G "NMake Makefiles"  -DANDROID_ABI=x86 -DANDROID_PLATFORM=android-30  -DCMAKE_TOOLCHAIN_FILE=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake -DANDROID_NDK=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529
cmake -S . -B build -G "NMake Makefiles"  -DANDROID_ABI=x86_64 -DANDROID_PLATFORM=android-30  -DCMAKE_TOOLCHAIN_FILE=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake -DANDROID_NDK=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529
cmake -S . -B build -G "NMake Makefiles"  -DANDROID_ABI=armeabi-v7a -DANDROID_PLATFORM=android-30  -DCMAKE_TOOLCHAIN_FILE=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake -DANDROID_NDK=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529
cmake -S . -B build -G "NMake Makefiles"  -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-30  -DCMAKE_TOOLCHAIN_FILE=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529/build/cmake/android.toolchain.cmake -DANDROID_NDK=C:/Users/xiaca/AppData/Local/Android/Sdk/ndk/21.4.7075529
# 鸿蒙编译方法
# 要进入vs2022 或者2019的 x64编译控制台,保证能运行nmake
# armeabi-v7a
#[[
cmake -S . -B build -G "NMake Makefiles" -DOHOS_ARCH=armeabi-v7a -DCMAKE_TOOLCHAIN_FILE=D:\openharmony_sdk\native\3.1.6.6\build\cmake\ohos.toolchain.cmake
cmake -S . -B build -G "NMake Makefiles" -DCMAKE_TOOLCHAIN_FILE=D:\openharmony_sdk\native\3.1.6.6\build\cmake\ohos.toolchain.cmake
]]

CMake基础使用语法

# CMakeLists.txt 编写

# cmake版本
cmake_minimum_required(VERSION 3.0)

# 定义当前工程名称
project(demo)

#添加头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty)
include_directories(
	"./include"
	"./config/"
)

# 设置debug编译选项
set(CMAKE_BUILD_TYPE "Debug")

# 设置C++11标准
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# linux上配置调试信息
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 添加对应的debug宏  #if defined(OS_LINUX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOS_LINUX ")

# 编译的时候打印信息  FATAL_ERROR:错误会停止后续执行
message(STATUS "message: " ${PROJECT_SOURCE_DIR})

# CMAKE_CXX_FLAGS 匹配指定宏
if (${CMAKE_CXX_FLAGS} MATCHES "OS_LINUX")
	message(STATUS "LINUX")
else()
	message(STATUS "other")
endif()

# 设置自定义变量,并匹配
set(PLATFORM "iOS")
if (${PLATFORM} MATCHES "iOS")
  message(STATUS "iOS编译对应的库")
endif()

# 设置可执行文件最终存储的路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 设置静态库或者动态库输出目录
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 加载子目录
add_subdirectory(src/server)
add_subdirectory(other_dir)

子目录的CMakeLists.txt编写

# 需要编译的源文件
file(GLOB SERVER_SRC_LIST
    "*.cpp"
    "db/*.cpp"
)

# 定义变量,存储当前目录下的所有源文件
# aux_source_directory(. SERVER_SRC_LIST)

# 从源文件列表中移除某个文编不参与编译
list(REMOVE_ITEM SERVER_SRC_LIST
	"./client/test.cpp"
)

# 编译动态库
add_library(server_static SHARED ${SERVER_SRC_LIST})
# 生成静态库 (同时生成静态库和动态库输出库名称不能相同)
add_library(server STATIC ${SERVER_SRC_LIST})

# 生成可执行文件
add_executable(ChatServer ${SERVER_SRC_LIST})

# 设置链接库的寻找路径
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib)

# 可执行文件链接库
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient pthread)

# 设置目标属性,此例子是给ChatServer 设置属性 PUBLIC_HEADER = "public.h"
set_target_properties(ChatServer PROPERTIES 
    PUBLIC_HEADER "public.h"
    )

# make install 设置库文件和头文件的安装路径 DESTINATION:安装路径
INSTALL(TARGETS ChatServer
    ARCHIVE DESTINATION ${CMAKE_SOURCE_DIR}/bin
    LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/bin
    PUBLIC_HEADER DESTINATION ${CMAKE_SOURCE_DIR}/bin/include
)

# 给编译目标添加一些编译配置
target_compile_options(ChatServer PUBLIC -Wall -DLINUX -lpthread)

CMake常用的预定义变量

PROJECT_NAME : 通过 project() 指定项目名称
PROJECT_SOURCE_DIR : 工程的根目录
PROJECT_BINARY_DIR : 执行 cmake 命令的目录 
CMAKE_CURRENT_SOURCE_DIR : 当前 CMakeList.txt 文件所在的目录 
CMAKE_CURRENT_BINARY_DIR : 编译目录,可使用 add subdirectory 来修改 
EXECUTABLE_OUTPUT_PATH : 二进制可执行文件输出位置 
LIBRARY_OUTPUT_PATH : 库文件输出位置
BUILD_SHARED_LIBS : 默认的库编译方式 ( shared 或 static ) ,默认为 static 
CMAKE_C_FLAGS : 设置 C 编译选项
CMAKE_CXX_FLAGS : 设置 C++ 编译选项
CMAKE_CXX_FLAGS_DEBUG : 设置编译类型 Debug 时的编译选项 
CMAKE_CXX_FLAGS_RELEASE : 设置编译类型 Release 时的编译选项 
CMAKE_GENERATOR : 编译器名称
CMAKE_COMMAND : CMake 可执行文件本身的全路径 
CMAKE_BUILD_TYPE : 工程编译生成的版本, Debug / Release
CMAKE_LIBRARY_OUTPUT_DIRECTORY : linux 动态库so输出路径
CMAKE_ARCHIVE_OUTPUT_DIRECTORY : 归档输出路径(windows静态库lib、windows动态库lib文件、linux静态库.a)
CMAKE_RUNTIME_OUTPUT_DIRECTORY : 执行程序和dll动态库

结合Shell脚本

#!/bin/bash

# 编译选项
BUILD_TYPE=Release
# 编译平台
PLATFORM=OS64

show_help() {
    cat << EOF
usage: ${0##*/} [-h] [-p PLATFORM]

-h           display help
-d           build debug
-s           build sdk
-p PLATFORM  [OS|OS64|SIMULATOR|universal|ft|lx|amd|ft_uos|lx_uos|amd_uos|MAC]
EOF
}

buildTest() {
    echo "buildTest start"
    echo "BUILD_TYPE = ${BUILD_TYPE}"
    echo "PLATFORM = ${PLATFORM}"
}

# p: 说明后续可以跟参数, $OPTARG是传入的参数值
while getopts "hdsp:" opt
do
    case $opt in
        p)
            echo "-p 选项的值是:$OPTARG"
            PLATFORM="$OPTARG"
            buildTest
            ;;
        d)
            BUILD_TYPE=Debug
            ;;
        s)
            echo "build sdk"
            ;;
        h)
            echo "发现 -h 参数"
            show_help
            exit
            ;;
        \?)
            echo "未知选项:$opt"
            show_help
            ;;
    esac
done


rm -rf ./build
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
# 结合cmake toolchain使用
cmake .. -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_PATH -DCMAKE_BUILD_TYPE=$BUILD_TYPE

网站公告

今日签到

点亮在社区的每一天
去签到