CMake中lex预处理包含文件依赖配置失效问题排查
Lex预处理文件的CMake动态依赖配置方案
所有已知的lex发行版都不原生支持在lex文件(如规则段)中直接引入包含文件,通用实现方案是自行编写预处理器完成include逻辑展开。这类自定义预处理器需要正确配置构建依赖,保证被包含的文件变更时,能自动触发预处理步骤重新生成处理后的lex文件,再走后续的flex编译流程。
场景复现
现有配置如下:
- 原始lex文件为
configimpl.l,内部引入了aa.inc、bb.inc两个包含文件 - 预处理逻辑:通过Python脚本将源码目录下的原始
.l文件处理为生成目录下的.l文件,同时生成同名.corr附属文件 - 构建环境:CMake 3.22.1,生成器为NMake Makefiles
初始版本直接把已知的include文件写在add_custom_command的DEPENDS字段可以正常工作,但无法覆盖嵌套include的场景;尝试拆分独立步骤生成d依赖文件、通过DEPFILE参数传入的方案失效,修改aa.inc或bb.inc不会触发预处理步骤重跑。
初始失效的配置代码如下:
# 单独生成d文件的自定义命令 add_custom_command( COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/dep_lex.py ${CMAKE_CURRENT_LIST_DIR}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.d ${GENERATED_SRC}/${lex_file}.l ${CMAKE_CURRENT_LIST_DIR} DEPENDS ${CMAKE_CURRENT_LIST_DIR}/dep_lex.py ${CMAKE_CURRENT_LIST_DIR}/${lex_file}.l OUTPUT ${GENERATED_SRC}/${lex_file}.d ) set_source_files_properties(${GENERATED_SRC}/${lex_file}.d PROPERTIES GENERATED 1) # 预处理的自定义命令 add_custom_command( COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/pre_lex.py ${CMAKE_CURRENT_LIST_DIR}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.corr ${CMAKE_CURRENT_LIST_DIR} DEPENDS ${CMAKE_CURRENT_LIST_DIR}/pre_lex.py ${CMAKE_CURRENT_LIST_DIR}/${lex_file}.l DEPFILE ${GENERATED_SRC}/${lex_file}.d OUTPUT ${GENERATED_SRC}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.corr )
对应生成的d文件内容为:
${GENERATED_SRC}/generated_src/configimpl.l: ${CMAKE_CURRENT_LIST_DIR}/aa.inc ${GENERATED_SRC}/generated_src/configimpl.l: ${CMAKE_CURRENT_LIST_DIR}/bb.inc
配置失效核心原因
- DEPFILE不能是独立自定义命令的输出
CMake的DEPFILE参数要求依赖文件必须由当前自定义命令在执行过程中生成,不能提前通过单独的add_custom_command生成。拆分d文件生成步骤后,NMake生成器无法在构建阶段正确关联预生成的d文件和预处理命令的依赖关系,第一次构建时甚至会因为找不到d文件直接跳过依赖检查。 - depfile规则格式不符合NMake解析逻辑
NMake对依赖规则的解析和GNU Make有差异:如果同一个目标分多行写依赖声明、且规则后没有附带执行命令,后面的依赖行会覆盖前面的内容。上述d文件把aa.inc、bb.inc拆成两行写,最终NMake只会识别最后一行的bb.inc作为依赖,aa.inc的依赖直接丢失。 - 输出列表未包含d文件
即使合并生成步骤,如果d文件没有出现在对应add_custom_command的OUTPUT列表里,CMake不会把它识别为该命令的生成产物,构建时可能出现文件找不到、或者d文件更新不触发命令重跑的问题。 - d文件中残留CMake变量
生成的d文件里直接写了${GENERATED_SRC}、${CMAKE_CURRENT_LIST_DIR}这类CMake变量,NMake本身无法解析CMake变量,会把带变量的路径当成无效路径直接忽略。
正确配置方案
把依赖扫描逻辑合并到预处理脚本中,让预处理脚本在生成处理后.l文件、.corr文件的同时,直接输出格式正确的d依赖文件,不需要单独拆分依赖扫描步骤。
CMake配置代码
add_custom_command( # 预处理脚本一次执行生成三个产物:处理后的.l文件、.corr附属文件、.d依赖文件 COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/pre_lex.py ${CMAKE_CURRENT_LIST_DIR}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.corr ${GENERATED_SRC}/${lex_file}.d ${CMAKE_CURRENT_LIST_DIR} DEPENDS ${CMAKE_CURRENT_LIST_DIR}/pre_lex.py ${CMAKE_CURRENT_LIST_DIR}/${lex_file}.l DEPFILE ${GENERATED_SRC}/${lex_file}.d OUTPUT ${GENERATED_SRC}/${lex_file}.l ${GENERATED_SRC}/${lex_file}.corr ${GENERATED_SRC}/${lex_file}.d ) set_source_files_properties(${GENERATED_SRC}/${lex_file}.d PROPERTIES GENERATED 1)
预处理脚本调整要点
- 扫描原始.l文件的所有include语句,递归追踪所有层级的嵌套包含文件
- 写入d文件前把所有CMake变量展开为绝对路径,不要在d文件中残留
${}格式的变量 - d文件的依赖规则把所有依赖文件写在同一行,格式参考:
C:/your/project/path/generated/generated_src/configimpl.l: C:/your/project/path/aa.inc C:/your/project/path/bb.inc C:/your/project/path/nested.inc - 每次执行预处理时同步更新d文件,不需要单独跑依赖扫描步骤
验证注意事项
- 第一次构建完成后,可以去生成目录下查看NMake生成的对应规则,确认所有.inc文件已经被加入到生成目录下configimpl.l的依赖列表中
- 不需要手动把.inc文件加入到编译目标的源文件列表,只要把预处理生成的.l文件传入FLEX_TARGET宏作为输入,依赖会自动通过DEPFILE向上传递
- 该配置同时兼容NMake、Ninja、Makefile等所有支持DEPFILE参数的CMake生成器
内容的提问来源于stack exchange,提问作者albert
相关产品推荐
相关产品推荐

