CMake 为源文件提供相对路径版本的 __FILE__

CMake 为源文件提供相对路径版本的 __FILE__

RayAlto OP

CMake 起初有一个 CMAKE_USE_RELATIVE_PATHS 可以用来告诉 CMake 给底层工具传相对路径,但 3.4 版本之后这个变量被移除了,导致这样的代码:

1
std::cout << __FILE__ << '\n';

会输出绝对路径,比如 /home/user/Projects/proj/src/foo.cpp ,前面的一串 /home/user/Projects/proj 都与项目无关,而且包含用户名 user ,一不小心就会像某个腾讯员工一样,在程序文件里塞进了自己的姓名,这是何等的社死。

之前一直没有在乎这种东西,因为一直没有(现在也没有)向别人提供编译好的二进制文件的需求,但最近折腾了 CMake 的各种细节,所以打算想办法解决一下这个问题

1. 拿到相对路径

无论如何要先能在 CMakeLists.txt 里推算出相对路径版本的 __FILE__ , CMake 有 file() 函数(因为我使用 CMake 3.9 ,所以不能使用 cmake_path ):

1
2
3
# 比如源文件为 `src`

file(RELATIVE_PATH src_relpath ${PROJECT_SOURCE_DIR} ${src})

这样输出的 src_relpath 就是相对 project() 函数所在目录的相对路径了

2. 拿到 Target 的所有源文件

要更方便地为 Target 的每个源文件设置相对路径版本的 __FILE__ ,最好是可以通过 Target 拿到每个源文件:

1
2
3
# 比如 Target 为 `target_name`

get_target_property(target_srcs "${target_name}" SOURCES)

这样输出的 target_srcs 就包含了 target_name 的所有源文件了,可以用 foreach() 遍历每个源文件:

1
2
3
foreach(target_src ${target_srcs})
# ...
endforeach()

3. 为源文件定义相对路径版本的 __FILE__

我个人并不想覆盖掉原汁原味的 __FILE__ ,所以打算另外定义一个 __REL_FILE__ 宏,表示 __FILE__ 的相对路径( RELative path )版本:

点击展开:之前的错误版本

)

1
set_source_files_properties(${target_src} PROPERTIES COMPILE_DEFINITIONS "__REL_FILE__=\"${target_src_relpath}\"")

set_property(APPEND) 更稳妥一些:

1
set_property(SOURCE ${target_src} APPEND PROPERTY COMPILE_DEFINITIONS "__REL_FILE__=\"${target_src_relpath}\"")

4. 跳过本身就是相对路径的源文件

这样拿到的 target_src 是来自 add_executable()add_library() 的,有时可能不是像这样精确的:

1
add_executable(foo ${PROJECT_SOURCE_DIR}/src/foo.cpp)

而是直接传相对路径:

1
add_executable(foo src/foo.cpp)

这样拿到的 target_src 本身就是相对路径,再对它调用 file(RELATIVE_PATH) 会报错,所以可以跳过这样的路径:

1
2
3
if(NOT IS_ABSOLUTE ${target_src})
# `target_src` 是相对路径
endif()

总结

总结到一起可以定义一个 target_define_relative_path 函数:

1
2
3
4
5
6
7
8
9
10
11
12
# Add a relative path version of the __FILE__ macro named __REL_FILE__ to all source files of the target.
function(target_define_relative_file target_name)
get_target_property(target_srcs "${target_name}" SOURCES)
foreach(target_src ${target_srcs})
if(IS_ABSOLUTE ${target_src})
file(RELATIVE_PATH target_src_relpath ${PROJECT_SOURCE_DIR} ${target_src})
else()
set(target_src_relpath ${target_src})
endif()
set_property(SOURCE ${target_src} APPEND PROPERTY COMPILE_DEFINITIONS "__REL_FILE__=\"${target_src_relpath}\"")
endforeach()
endfunction()

这样每个源文件都会有一个 __REL_FILE__ 宏,表示自己相对 project_root 的相对路径