能否基于已有多层CMakeLists自动生成conanfile.txt并解析依赖?
基于多层CMakeLists自动生成conanfile.txt的可行方案
可以实现从现有多层CMakeLists中提取依赖并生成conanfile.txt,但目前没有Conan官方原生工具支持,需要借助自定义脚本或CMake自省能力来完成,以下是具体思路:
一、利用CMake自省能力提取依赖
CMake本身提供了查询目标属性、缓存变量的接口,可以通过编写CMake脚本递归收集项目中的第三方依赖:
- 遍历项目所有目标,通过
get_target_property获取INTERFACE_LINK_LIBRARIES、INTERFACE_INCLUDE_DIRECTORIES等属性,过滤出外部依赖(排除项目内部目标)。 - 解析CMake缓存中的
*_FOUND、*_VERSION变量,提取通过find_package引入的依赖名称和版本号。 - 将收集到的依赖整理成conanfile.txt的
[requires]区块;Generator部分可根据CMake的使用习惯对应,比如用find_package则添加CMakeDeps,需要工具链配置则添加CMakeToolchain。
示例CMake脚本片段(用于收集依赖):
function(collect_conan_deps target) get_target_property(link_libs ${target} INTERFACE_LINK_LIBRARIES) if(link_libs) foreach(lib ${link_libs}) if(NOT TARGET ${lib} AND NOT lib MATCHES "^${PROJECT_NAME}_") # 假设依赖名称和Conan包名一致,需根据实际情况调整 list(APPEND CONAN_REQUIRES ${lib}) endif() if(TARGET ${lib}) collect_conan_deps(${lib}) endif() endforeach() endif() set(CONAN_REQUIRES ${CONAN_REQUIRES} PARENT_SCOPE) endfunction() # 遍历项目顶层目标 foreach(target ${PROJECT_NAME}_TARGETS) collect_conan_deps(${target}) endforeach() # 输出到临时文件 file(WRITE conanfile.txt "[requires]\n") foreach(req ${CONAN_REQUIRES}) file(APPEND conanfile.txt "${req}\n") endforeach() file(APPEND conanfile.txt "\n[generators]\nCMakeDeps\nCMakeToolchain\n")
二、用脚本解析CMakeLists文本
通过Python等脚本语言解析CMakeLists.txt的内容,提取关键依赖信息:
- 正则匹配
find_package、target_link_libraries等指令,提取依赖名称和版本。 - 递归遍历所有子目录的CMakeLists.txt文件,确保收集全量依赖。
示例Python脚本片段:
import os import re def find_all_cmake_files(root_dir): cmake_files = [] for dirpath, _, filenames in os.walk(root_dir): for fname in filenames: if fname == "CMakeLists.txt": cmake_files.append(os.path.join(dirpath, fname)) return cmake_files def extract_deps(cmake_files): requires = set() for fpath in cmake_files: with open(fpath, 'r', encoding='utf-8') as f: content = f.read() # 匹配带版本的find_package pkg_matches = re.findall(r'find_package\s*\(\s*(\w+)\s+([\d\.]+)\s*', content) for pkg, ver in pkg_matches: requires.add(f"{pkg}/{ver}") # 匹配不带版本的find_package pkg_matches_no_ver = re.findall(r'find_package\s*\(\s*(\w+)\s*(?!VERSION)', content) for pkg in pkg_matches_no_ver: if not any(pkg in req for req in requires): requires.add(pkg) return sorted(requires) if __name__ == "__main__": cmake_files = find_all_cmake_files(".") requires = extract_deps(cmake_files) with open("conanfile.txt", 'w') as f: f.write("[requires]\n") f.write("\n".join(requires)) f.write("\n\n[generators]\n") f.write("CMakeDeps\nCMakeToolchain\n")
三、注意事项
- 依赖名称映射:CMake中的依赖名称和Conan包名可能不一致(比如CMake的
OpenCV对应Conan的opencv/opencv),需要手动维护映射规则或在脚本中添加转换逻辑。 - 条件编译依赖:如果依赖是通过
if等条件指令引入的,静态解析脚本可能无法覆盖所有场景,需要结合CMake的配置结果(如缓存变量)来补充。 - 非标准依赖引入:对于通过
add_subdirectory直接引入的本地依赖,脚本无法自动识别为Conan包,需手动区分并调整。
内容的提问来源于stack exchange,提问作者DdA
相关产品推荐
相关产品推荐

