CHECK_LIBRARY_EXISTS的原理

起因

前两天不小心在StackOverflow上看到这个问题,因为最近一直想把一个用automake的项目改成用CMake,以便使用Clion,所以所有和CMake相关的input都愿意接受的。这个问题问的是leveldbCMakeLists.txt里面有一行:check_library_exists(snappy snappy_compress "" HAVE_SNAPPY),和预期的运行结果不一致,怎么排查?

过程

先看看CHECK_LIBRARY_EXISTS这个宏的参数定义:

1
CHECK_LIBRARY_EXISTS (LIBRARY FUNCTION LOCATION VARIABLE)

对于CMake,我之前调试的手段基本上是加message,打印调试大法。但是对于CMake自身定义的宏,这个方法明显是不适用的,只好看看CMake自身支持哪些flag了。

man cmake可以看到很多flag,比较符合我们需求的首先是--debug-outputcmake --debugoutput ..之后能看到:

1
2
3
-- Looking for snappy_compress in snappy
Called from: [2] /usr/local/Cellar/cmake/3.8.2/share/cmake/Modules/CheckLibraryExists.cmake
[1] CMakeLists.txt

CheckLibraryExists.cmake里面可以看到调用了宏try_compile

1
2
3
4
5
6
7
8
9
10
try_compile(${VARIABLE}
${CMAKE_BINARY_DIR}
${_cle_source}
COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
LINK_LIBRARIES ${CHECK_LIBRARY_EXISTS_LIBRARIES}
CMAKE_FLAGS
-DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_LIBRARY_EXISTS_DEFINITION}
-DLINK_DIRECTORIES:STRING=${LOCATION}
OUTPUT_VARIABLE OUTPUT)
unset(_cle_source)

那么现在问题来了,try_compile到底最终调用了什么指令呢?

try_compile文档里面可以看到,上面使用的是Try Compiling Source Files这种模式,在文档的最后有这样一句话:

In this version all files in <bindir>/CMakeFiles/CMakeTmp will be cleaned automatically. For debugging, --debug-trycompile can be passed to cmake to avoid this clean

如果加了--debug-trycompile,就可以从<bindir>/CMakeFiles/CMakeTmp这个目录里面看到最终的编译/链接命令是什么了。

接近

加上--debug-trycompile参数执行cmake

1
cmake --debugoutput --debug-trycompile ..

进入CMakeFiles/CMakeTmp,可以看到, 这下面是一个小的CMake工程,Makefile已经有了,还等啥呢,make V=1来一发,我们需要的指令赫然在列:

1
cc -DCHECK_FUNCTION_EXISTS=snappy_compress -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/cmTC_377d3.dir/CheckFunctionExists.c.o -o cmTC_377d3 -lsnappy

CheckFunctionExists.c.oCMake中的CheckFunctionExists.c编译出来的。所以如果希望抛开cmake先,去验证check_library_exists(snappy snappy_compress "" HAVE_SNAPPY),可以运行下面指令:

1
cc -DCHECK_FUNCTION_EXISTS=snappy_compress -Wl,-search_paths_first -Wl,-headerpad_max_install_names -o check_snappy_exist CheckFunctionExists.c -lsnappy

什么?你问那几个编译参数是什么:

1
2
3
4
5
6
7
8
9
10
11
## 这个后面的参数是传给链接器的,相关的参数,还有 Wa / Wp
-Wl,<args>
Pass the comma separated arguments in args to the linker.
## 与这个参数相对的是search_dylibs_first,是不是一看就明白了 :)
-search_paths_first
This is now the default (in Xcode4 tools). When processing -lx the linker now searches each directory in its library search paths for `libx.dylib' then `libx.a' before the moving on to the next path in the library search path.
## hmmm .. don't know yet
-headerpad_max_install_names
Automatically adds space for future expansion of load commands such that all paths could expand to MAXPATHLEN. Only useful if intend to run install_name_tool to alter the load commands later.

总结

如果保持一周两篇的节奏,目标还是可以达成的。最近老是感觉习惯不太好,有一才有二,有二才有三,一没有迈出去,很多事情就走从谈起了。贴一下<Refactoring: Improving the Design of Existing Code>里面的一段话:

It reminds me of a statement Kent Beck often makes about himself, “I’m not a great programmer; I’m just a good programmer with great habits.” Refactoring helps me be much more effective at writing robust code.