cmake踩坑&爬坑
最近实习了,每天都要和cmake打交道,记录一些时常要用的东西和遇到的问题。
cmake 最小模板
cmake_minimum_required(VERSION 3.9) |
add_subdirectory
用add_subdirectory
可以很方便的控制是否编译子目录,比如我们将测试代码放到test
中,在根目录的cmakelist里面选择是否编译单元测试文件。
利用通配符找到源文件
file(GLOB SOURCE_FILES ${CMAKE_SOURCE_DIR}/test_*.cpp) |
macro使用
变量 | 说明 |
---|---|
ARGV# | # 是一个下标,0 指向第一个参数,累加 |
ARGV | 所有的定义时要求传入的参数 |
ARGN | 定义时要求传入的参数以外的参数列表 |
ARGC | 传入的实际参数的个数,也就是调用函数是传入的参数个数 |
当要收集额外参数的时候,不能直接在宏的入口处写参数,因为那里的参数是必须传入的,只能直接传入然后用if进行解析。
macro(add_code name)
if(${ARGC} GREATER 2)
set(gen_name ${ARGV1})
set(gen_variable ${ARGV2})
else()
set(gen_name ${name})
set(gen_variable "")
endif()
add_custom_target(gen_${name}
COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gen_all -g halide_${name} -n halide_${gen_name} -o ${CMAKE_CURRENT_SOURCE_DIR}/kernels -e c_header,assembly,schedule,stmt_html target=host-no_asserts-no_bounds_query "${gen_variable}")
add_dependencies(gen_${name} gen_all)
endmacro()
空格被转义
我想输入一个参数为"kernel_width=3 kernel_height=3"
然后执行命令,但是我发现执行命令的时候变成了这样:
kernel_width=3\ kernel_height=3
这个是因为cmake为了区别于他多个变量赋值的问题,我们可以用两个方式解决
set(testfiles "test1" "test2") \\ 1. 直接多个变量赋值
set(testfiles "kernel_width=3;kernel_height=3") \\ 2. 使用分号隔开
检查正确性
有时候我们可能需要检查cmake的内部变量是不是有错误,要加message比较麻烦,我们可以这样:
make VERBOSE=1
为某个文件添加依赖,自定义生成
使用custom
command,然后把生成的src添加到一个新的的target中,然后目标target依赖这个target。
要注意,我们生成的源文件不能直接加到目标target中,因为一开始没有生成代码,会导致cmake
config出错,得找找方法去解决一下。 add_custom_command(
OUTPUT testData.cpp
COMMAND reswrap
ARGS testData.src > testData.cpp
DEPENDS testData.src
)
add_custom_target(evaluator_k510.update SOURCES ${KERNEL_SRCS})
add_dependencies(evaluator_k510 evaluator_k510.update)
如果是在没有依赖,那么可以自己加个dummy.cpp
然后强制把需要生成的文件给添加上
cmake默认target输出位置
在编译时通过add_executable()
生成的可执行文件,以及利用add_library( SHARED )
的文件,默认输出到CMAKE_RUNTIME_OUTPUT_DIRECTORY
,通常是build/bin
目录。
cmake导出pseudo target,并进行调用
我首先用add_custom_command
生成了一个lib,但是这个lib不是cmake构建的,所以不能直接被cmake调用,并且如果他是一个lib,必须要先写个add_library
制作一个伪目标,然后再把他真实的lib添加上去,然后才能正常使用这个target。
add_library(hkg_${os_name}_runtime INTERFACE)
target_link_libraries(hkg_${os_name}_runtime INTERFACE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/halide_runtime_${os_name}.${suffix} -ldl)
add_library(hkg::${os_name}_runtime ALIAS hkg_${os_name}_runtime)
更加复杂的例子在这里https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#interface-libraries
cmake安装target设置其名字
当我们设置自定义目标的时候,用别名ALIAS
可以指定在整个构建过程中的target的使用名字,但是如果这个target需要导出(也就是被第三方库使用时),他的名字还得使用set_target_properties
设置一下。
add_library(Halide::Generator ALIAS Halide_Generator)
set_target_properties(Halide_Generator PROPERTIES EXPORT_NAME Generator)
cmake安装目录路径
cmake真的处处是坑,如果想install一个目录下的所有东西,那么必须这样写:
install(DIRECTORY include/
DESTINATION include)/
安装后就会出现include/include
的情况
cmake函数返回值
cmake里面是没有函数返回值的,所以必须要在函数中设置变量的值,同时指定他的scope,同时还需要注意设置变量的写法。或者说传递变量的名字然后用${name}的方法完成了c中类似指针的功能🤣。
FUNCTION(MY_FUNC_WITH_RET ret) |
cmake写入多行include
还是利用configure_file,不过需要自己手动处理比较多的东西
简单测试,在.h.in文件中添加@linux_include_list@
块之后执行cmake,就可以得到正确的include了。
cmake_minimum_required(VERSION 3.20)
project(fuck)
set(base_name "conv2d")
set(os_name "linux")
set(header_list "")
list(APPEND feature_list
"avx512"
"avx2"
"sse41"
"bare")
list(APPEND full_feature_list
"-sse41-avx-f16c-fma-avx2-avx512"
"-avx-avx2-f16c-fma-sse41"
"-avx-f16c-sse41"
"")
foreach(feature full_feature IN ZIP_LISTS feature_list full_feature_list)
message("${feature} ${full_feature}")
list(APPEND header_list "${base_name}-${os_name}-${feature}.h")
endforeach()
set(linux_include_list "")
foreach(header IN LISTS header_list)
set(linux_include_list "${linux_include_list}\r\n#include \"${header}\"")
endforeach()
# set(linux_include_list ${header_list})
set(windows_include_list ${header_list})
set(osx_include_list ${header_list})
configure_file(src/conv2d.h.in ${CMAKE_SOURCE_DIR}/src/conv2d.h)
cmake获取target 的library path
我遇到了一个奇怪的bug,就是使用add_library
添加了一组link的对象接口之后,被链接的库没有被正确写入到target中,但是cmake又无法调试,所以只能从target中获取属性再打印出来看看,因为我的link
library是interface的模式,所以要获取interface的property
get_target_property(OUT hkg_linux_src INTERFACE_LINK_LIBRARIES)
message(STATUS ${OUT})
然后我就发现了问题,即我们在编写target_link_libraries
的时候,所有的字符串都是raw模式的,也就是说我们不需要加上双引号,我原来的写法如下
target_link_libraries(hkg_linux_src INTERFACE $<BUILD_INTERFACE:"${LINUX_SRCS}">
$<INSTALL_INTERFACE:"${LINUX_SRCS}">)$<BUILD_INTERFACE:"/home/workspace/kernels_generator/include/hkg/generated_kernels/halide_conv2d_7x7_linux_bare.o">
所以需要这样写,就可以避免这个问题了。 target_link_libraries(hkg_linux_src INTERFACE $<BUILD_INTERFACE:${LINUX_SRCS}>
$<INSTALL_INTERFACE:${LINUX_SRCS}>)
undefined reference to `pthread_create'
这个问题也老奇怪了,明明添加了-lphread
但是还是报错,发现要使用-phread
才可以。
macos中的rpath问题
RPATH就是可执行文件寻找他动态链接库的路径,在linux中,大多数命令都是以以下顺序去搜索动态链接库的
- RPATH
- LD_LIBRARY_PATH
- RUNPATH
- /etc/ld.so.conf
- builtin directories
通常我们设置LD_LIBRARY_PATH为给可执行程序提供库的路径,但是如果有同时存在多个版本的库都在路径中,同时我们还需要链接不同版本的库,那应该怎么办? 这时候就需要设置RPATH或者RUNPATH,提供一个指定的路径/
但是macos的情况有些不同,他的连接器 dyld 使用每个 dylib 的完整路径来定位依赖的动态库。
可执行文件中用完整路径记录他的依赖库: ❯ otool -L bin/can_use_target.generator
bin/can_use_target.generator:
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.100.5)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 905.6.0)
conan支持riscv
我今天才发现Conan
build的时候没有按照编译器选择对应的arch,然后看了一下,他们官方居然都没有做rv的支持.
他可选的setting中没有rv这个平台. 最终的cmake中的修改如下:
set(TARGET_ARCH x86_64)
if(ENABLE_K510_RUNTIME AND BUILDING_TEST)
# NOTE you need add `riscv64` in `~/.conan/settings.yml` `arch, arch_build, arch_target` item.
# refer from https://github.com/conan-io/cmake-conan/issues/307
set(TARGET_ARCH "riscv64")
endif()
conan_cmake_run(BASIC_SETUP
CONANFILE conanfile-runtime.txt
SETTINGS compiler.cppstd=17
BUILD missing
ARCH ${TARGET_ARCH}
ENV CC=${CMAKE_C_COMPILER}
ENV CXX=${CMAKE_CXX_COMPILER}
ENV CFLAGS=${CMAKE_C_FLAGS}
ENV CXXFLAGS=${CMAKE_CXX_FLAGS})
include(${CMAKE_BINARY_DIR}/conan_paths.cmake)