cmake-自动识别新增子模块

实际的项目中可能会有这种需求,随着项目的进行,会有新增的子模块,如果每新增一个子模块,顶层CMakeLists.txt都要同步修改一次,一般工程代码加入了版本控制,那么顶层CMakeList.txt每次都要提交,这种低效的重复性动作就要坚决干掉。要怎么干呢?就是本文要介绍的方法。同时也会详细介绍涉及的小知识点。

目录结构

|-- build
|-- CMakeLists.txt
`-- src
    |-- inc
    |-- main.c
    |-- module1
    |   `-- CMakeLists.txt
    `-- module2
        `-- CMakeLists.txt

CMakeLists.txt

顶层CMakeLists.txt

cmake_minimum_required(VERSION 3.12)
project(test08)

# 获取子模块目录
execute_process(
    COMMAND ls ${PROJECT_SOURCE_DIR}/src
    OUTPUT_VARIABLE mydirs
    )
message("mydirs: ${mydirs}") # 打印文件名和目录名
string(REPLACE "\n" ";" dirs_list ${mydirs})

# 编译下一级所有子模块
foreach(item ${dirs_list})
    if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/src/${item} AND (NOT (${item} STREQUAL "inc")))
        message("item:${item}") # 打印过滤后的模块名
        add_subdirectory(${PROJECT_SOURCE_DIR}/src/${item})
    endif()
endforeach()

aux_source_directory(${PROJECT_SOURCE_DIR}/src src_dirs)
add_executable(test ${src_dirs})

第5-10行 获取src目录下的 文件名和目录名列表;

第13-18行 剔除文件名和需要排除的目录名,剩下的目录名即为模块名,然后通过add_subdirectory()编译子模块。

如此,即可实现,新增目录名,而不同修改顶层CMakeLists.txt,能自动"识别"新增模块并编译。

简单起见,modulex下的CMakeLists.txt仅使用message打印一下,表示有被执行到。

module1/CMakeLists.txt

message("111-module1")

module2/CMakeLists.txt

message("222-module2")

接下来,我们先cmake一下,看看整体的效果:
在这里插入图片描述

假设现在需要新增一个module3,我们只需要将新增的module3加入src目录下即可,而其它部分不需要修改。
在这里插入图片描述

ok,到这一步相信你已经会使用了,但是这背后的原理是怎样的呢?继续前进。。。

execute_process()

execute_process(COMMAND <cmd1> [<arguments>]
                [COMMAND <cmd2> [<arguments>]]...
                [WORKING_DIRECTORY <directory>]
                [TIMEOUT <seconds>]
                [RESULT_VARIABLE <variable>]
                [RESULTS_VARIABLE <variable>]
                [OUTPUT_VARIABLE <variable>]
                [ERROR_VARIABLE <variable>]
                [INPUT_FILE <file>]
                [OUTPUT_FILE <file>]
                [ERROR_FILE <file>]
                [OUTPUT_QUIET]
                [ERROR_QUIET]
                [COMMAND_ECHO <where>]
                [OUTPUT_STRIP_TRAILING_WHITESPACE]
                [ERROR_STRIP_TRAILING_WHITESPACE]
                [ENCODING <name>]
                [ECHO_OUTPUT_VARIABLE]
                [ECHO_ERROR_VARIABLE])

execute_process命令的语法要素非常多,这里不一一介绍,仅介绍和主题相关的内容。execute_process的作用简单说就是启动一个子进程执行COMMAND命令,并能获取COMMAND命令的执行结果及输出信息。

COMMAND <cmd1> [<arguments>]

CMake executes the child process using operating system APIs directly. All arguments are passed VERBATIM to the child process. No intermediate shell is used, so shell operators such as > are treated as normal arguments. (Use the INPUT_*, OUTPUT_*, and ERROR_* options to redirect stdin, stdout, and stderr.)

cmd1可以是linux命令,COMMAND需要注意的是不支持命令行交互,需要用INPUT_*, OUTPUT_*, and ERROR_*来重定向stdin, stdout, and stderr。

OUTPUT_VARIABLE <variable>, ERROR_VARIABLE <variable>

The variable named will be set with the contents of the standard output and standard error pipes, respectively. If the same variable is named for both pipes their output will be merged in the order produced.

OUTPUT_VARIABLE 对应的变量用来接收cmd的标准输出。如果OUTPUT_VARIABLE 和ERROR_VARIABLE的变量名相同,那么,标准输出和标准错误输出的内容会按产生顺序 保存到该变量中。

如顶层CMakeLists.txt中的

execute_process(
    COMMAND ls ${PROJECT_SOURCE_DIR}/src
    OUTPUT_VARIABLE mydirs
    )

ls ${PROJECT_SOURCE_DIR}/src 命令的标准输出结果 就保存在 mydirs变量中。

由于mydirs变量的值是’inc\nmain.c\nmodule1\nmodule2\nmodule3\n’,而期望得到的是一个列表,所以还需要对mydirs进行处理。因为mydirs的值是字符串,所以要用到string命令。

string()

cmake的字符串(string)操作也比较多,这里仅介绍替换操作。

string(REPLACE <match-string> <replace-string> <out-var> <input>...)

<input>中的所有和<match-string>匹配的字符(串)替换为<replace-string>的字符(串),然后将替换后的字符串保存到<out-var>中。

举个栗子:

string(REPLACE "\n" ";" dirs_list ${mydirs})

已知mydirs变量的值是’inc\nmain.c\nmodule1\nmodule2\nmodule3\n’,就是将其中的’\n’替换为’;’,最后保存在dirs_list中,其值为"inc;main.c;module1;module2;module3;"。

得到dirs_list后,发现里面有我们不想要的内容(inc;main.c),接下来,就将这些内容过滤掉;显然,要过滤掉列表的部分元素,得遍历一遍该列表,那么cmake中可以通过foreach命令来实现。

foreach()

foreach(<loop_var> <items>)
  <commands>
endforeach()

<items>是一个列表,里面的元素可用分号或空格隔开。foreach和endforeach必须要成对。每一次循环,都将取出items列表中的一个元素存入loop_var中,直至遍历完成,这是正常的一般依次遍历控制流程。你也可以使用breakcontinue

举个栗子:

foreach(item ${dirs_list})
    if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/src/${item} AND (NOT (${item} STREQUAL "inc")))
        message("item:${item}") # 打印过滤后的模块名
        add_subdirectory(${PROJECT_SOURCE_DIR}/src/${item})
    endif()
endforeach()

已知dirs_list的值为"inc;main.c;module1;module2;module3;",IS_DIRECTORY 判断 ${PROJECT_SOURCE_DIR}/src/${item}是否为目录,同时(NOT (${item} STREQUAL "inc"))过滤inc目录;这样就完成了子模块的"识别",然后通过add_subdirectory完成子模块的编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值