CMakeLists实战解读--YouCompleteMe
原文转载自:Ricky.K http://www.cnblogs.com/rickyk/p/3877238.html
个人一直有一个想法,就是想出一系列关于CMakeLists.txt国外经典例子的实战解读。因为国内关于CMake的介绍和用法少之又少,再加上CMake本身对于实践能力的要求也比较高,过于理论化的学习只会让读者停留在Hello World和超级项目之间(其实就是理论知识要么简单很容易,单个cpp或者单个lib,或者例子复杂的要死,直接带动整个超大过程类似KDE这种),所以,我觉得我应该带领读者去学习一些首先本身不会太简单但也不至于复杂到无从下手的经典的国外的example,让读者更加深入学习和了解CMake的同时,也让我能有更好的学习机会,这也是我开Ricky.K这个bolg,记录博客写下工作心得的另一个重要原因。因此这个系列我会带上一系列经典的CMakeLists.txt的经典例子,并且带上我的个人解读,读者如果觉得有错误或者有想法可以跟我留言交流。
废话不多说,Ricky就带领大家学习我们这个系列第一个题目--Vim补全神级插件YouCompleteMe的CMakeLists.txt.先介绍点之前我介绍这款插件的安装过程中遇到的问题,实际作者给出了一个具体的install.sh方便大家进行安装,你可以输入install.sh --help具体来查看,里面有--clang-completer(使用Clang补全)和--system-libclang(使用系统自带的libclang.so,这里需要将LD_LIBRARY_PATH设置好,因为后期的CMake脚本会去find_library这个环境变量里的libclang.so,在这里我前期只是简单的加了点PATH的维护,导致之后find_library一直去找的系统下的libclang,导致一直出错)还有--omnisharp-completer。
其实说白了,安装这个插件最重要的两个变量就是在这个插件中CMakeLists.txt中的USE_CLANG_COMPLETER和LIBCLANG_TARGET,前者被打开之后会添加相应的ClangCompleter头文件和宏USE_CLANG_COMPLETER,后者会再最后target_link给ycm_core.so的时候去加载相应路径下的libclang.so,其实说白了,你只要将这两个变量自己维护好,脱离脚本安装,乃至脱离作者介绍的CMake安装都没有任何问题。下面正式进入正题
1 # CMake要求的最低版本号 2 cmake_minimum_required( VERSION 2.8 ) 3 4 # 项目名称 5 project( ycm_support_libs ) 6 # 设置客户端lib和服务端lib的变量名称 7 set( CLIENT_LIB "ycm_client_support" ) 8 set( SERVER_LIB "ycm_core" ) 9 10 # 设置Python的版本号变量 11 set( Python_ADDITIONAL_VERSIONS 2.7 2.6 ) 12 # 进行Python包的查找,这里是REQUIRED表示必须 13 find_package( PythonLibs 2.6 REQUIRED ) 14 15 # 如果Python版本号低于3.0.0就进行FATAL_ERROR的出错信息 16 if ( NOT PYTHONLIBS_VERSION_STRING VERSION_LESS "3.0.0" ) 17 message( FATAL_ERROR 18 "CMake found python3 libs instead of python2 libs. YCM works only with " 19 "python2.\n" ) 20 endif() 21 22 # 各种option 23 option( USE_DEV_FLAGS "Use compilation flags meant for YCM developers" OFF ) 24 # 这个变量就是我上文讲到的很关键的一个变量,来判断当前用户需要不需要libclang 25 option( USE_CLANG_COMPLETER "Use Clang semantic completer for C/C++/ObjC" OFF ) 26 # install.sh中的--system-libclang就与这个变量进行交互 27 option( USE_SYSTEM_LIBCLANG "Set to ON to use the system libclang library" OFF ) 28 # YCM作者推荐的用法,在这里直接写入Clang的相关路径 注意这里的CACHE PATH,表示当用户如果命令行 29 # 进行指定,那优先会去读用户的命令行,而不是用这里的set,并且把相关的值写入Cache中 30 set( PATH_TO_LLVM_ROOT "" CACHE PATH "Path to the root of a LLVM+Clang binary distribution" ) 31 # YCM作者推荐的另外一种安装方法,直接将libclang.so全路径写死 32 set( EXTERNAL_LIBCLANG_PATH "" CACHE PATH "Path to the libclang library to use" ) 33 34 # 如果你使用libclang但是没有指定用不用系统的libclang,没有指定llvm_root,没有指定额外的libclang.so,那么就会带你去下载 35 if ( USE_CLANG_COMPLETER AND 36 NOT USE_SYSTEM_LIBCLANG AND 37 NOT PATH_TO_LLVM_ROOT AND 38 NOT EXTERNAL_LIBCLANG_PATH ) 39 message( "Downloading Clang 3.4" ) 40 41 # 这就是llvm官网的3.4下载路径 42 set( CLANG_URL "http://llvm.org/releases/3.4" ) 43 44 # 如果当前客户端是苹果 Mac OS X 45 if ( APPLE ) 46 # 设置Clang的文件夹名称 47 set( CLANG_DIRNAME "clang+llvm-3.4-x86_64-apple-darwin10.9" ) 48 # 设置Clang的MD5校验码 49 set( CLANG_MD5 "4f43ea0e87090ae5e7bec12373ca4927" ) 50 # 设置Clang文件名称为之后加上tar.gz 51 set( CLANG_FILENAME "${CLANG_DIRNAME}.tar.gz" ) 52 else() 53 # 如果是64位平台 54 if ( 64_BIT_PLATFORM ) 55 # 设置Clang的文件夹名称 56 set( CLANG_DIRNAME "clang+llvm-3.4-x86_64-unknown-ubuntu12.04" ) 57 # 设置Clang的MD5校验码 58 set( CLANG_MD5 "6077459d20a7ff412eefc6ce3b9f5c85" ) 59 # 设置Clang文件名称为之后加上tar.gz 60 set( CLANG_FILENAME "${CLANG_DIRNAME}.tar.xz" ) 61 else() 62 # 表示此时为32位的Linux,下载3.3版本 63 message( "No pre-built Clang 3.4 binaries for 32 bit linux, " 64 "downloading Clang 3.3" ) 65 set( CLANG_URL "http://llvm.org/releases/3.3" ) 66 # 设置Clang的文件夹名称 67 set( CLANG_DIRNAME "clang+llvm-3.3-i386-debian6" ) 68 # 设置Clang的MD5校验码 69 set( CLANG_MD5 "415d033b60659433d4631df894673802" ) 70 # 设置Clang文件名称为之后加上tar.gz 71 set( CLANG_FILENAME "${CLANG_DIRNAME}.tar.bz2" ) 72 endif() 73 endif() 74 75 # 下载命令 76 file( 77 DOWNLOAD "${CLANG_URL}/${CLANG_FILENAME}" "./${CLANG_FILENAME}" 78 SHOW_PROGRESS EXPECTED_MD5 "${CLANG_MD5}" 79 ) 80 81 # 文件名正则表达式匹配,进行相应的解压 82 if ( CLANG_FILENAME MATCHES ".+bz2" ) 83 # 执行相关的外部命令 tar 84 execute_process( COMMAND tar -xjf ${CLANG_FILENAME} ) 85 elseif( CLANG_FILENAME MATCHES ".+xz" ) 86 execute_process( COMMAND tar -xJf ${CLANG_FILENAME} ) 87 else() 88 execute_process( COMMAND tar -xzf ${CLANG_FILENAME} ) 89 endif() 90 91 # 设置PATH_TO_LLVM_ROOT的路径为当前CMake二进制路径下的Clang目录 92 set( PATH_TO_LLVM_ROOT "${CMAKE_CURRENT_BINARY_DIR}/../${CLANG_DIRNAME}" ) 93 endif() 94 95 # 如果设置了PATH_TO_LLVM_ROOT或者用户使用系统libclang或者有额外的libclang,就开启USE_CLANG_COMPLETER 96 # 这个变量我上文提过,很关键 97 if ( PATH_TO_LLVM_ROOT OR USE_SYSTEM_LIBCLANG OR EXTERNAL_LIBCLANG_PATH ) 98 set( USE_CLANG_COMPLETER TRUE ) 99 endif() 100 101 # 开始使用这个变量,如果用户确定使用libclang,但是没有root没有系统clang没有额外clang,那么 102 # 进行错误性提示 103 if ( USE_CLANG_COMPLETER AND 104 NOT PATH_TO_LLVM_ROOT AND 105 NOT USE_SYSTEM_LIBCLANG AND 106 NOT EXTERNAL_LIBCLANG_PATH ) 107 message( FATAL_ERROR 108 "You have not specified which libclang to use. You have several options:\n" 109 " 1. Set PATH_TO_LLVM_ROOT to a path to the root of a LLVM+Clang binary " 110 "distribution. You can download such a binary distro from llvm.org. This " 111 "is the recommended approach.\n" 112 " 2. Set USE_SYSTEM_LIBCLANG to ON; this makes YCM search for the system " 113 "version of libclang.\n" 114 " 3. Set EXTERNAL_LIBCLANG_PATH to a path to whatever " 115 "libclang.[so|dylib|dll] you wish to use.\n" 116 "You HAVE to pick one option. See the docs for more information.") 117 endif() 118 119 # 进行用户提醒,提醒用户当前是否使用libclang 120 if ( USE_CLANG_COMPLETER ) 121 message( "Using libclang to provide semantic completion for C/C++/ObjC" ) 122 else() 123 message( "NOT using libclang, no semantic completion for C/C++/ObjC will be " 124 "available" ) 125 endif() 126 127 # 如果设置了root就设置CLANG_INCLUDES_DIR为root下的include 128 # 否则CLANG_INCLUDES_DIR为CMake下llvm/include 129 if ( PATH_TO_LLVM_ROOT ) 130 set( CLANG_INCLUDES_DIR "${PATH_TO_LLVM_ROOT}/include" ) 131 else() 132 set( CLANG_INCLUDES_DIR "${CMAKE_SOURCE_DIR}/llvm/include" ) 133 endif() 134 135 # 如果当前的include路径不是绝对路径 136 if ( NOT IS_ABSOLUTE "${CLANG_INCLUDES_DIR}" ) 137 # 设置它为绝对路径 138 get_filename_component(CLANG_INCLUDES_DIR 139 "${CMAKE_BINARY_DIR}/${CLANG_INCLUDES_DIR}" ABSOLUTE) 140 endif() 141 142 # 如果没有额外的libclang,但是有root 143 if ( NOT EXTERNAL_LIBCLANG_PATH AND PATH_TO_LLVM_ROOT ) 144 if ( MINGW ) # 如果是MINGW 145 # 设置libclang的寻找路径(后面的find_library会去寻找) 146 set( LIBCLANG_SEARCH_PATH "${PATH_TO_LLVM_ROOT}/bin" ) 147 else() 148 set( LIBCLANG_SEARCH_PATH "${PATH_TO_LLVM_ROOT}/lib" ) 149 endif() 150 151 # 这里TEMP会被find_library去寻找clang,和libclang两个so,并且复制路径给TEMP 152 find_library( TEMP NAMES clang libclang 153 PATHS ${LIBCLANG_SEARCH_PATH} 154 NO_DEFAULT_PATH ) 155 156 message("temp is ${TEMP}") 157 # 设置额外的libclang为这个路径 158 set( EXTERNAL_LIBCLANG_PATH ${TEMP} ) 159 endif() 160 161 # 如果当前为苹果,设置额外的flag 162 if ( APPLE ) 163 set( CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem " ) 164 endif() 165 166 # 如果用户使用系统自带的boost 167 if ( USE_SYSTEM_BOOST ) 168 # 进行find boost命令,会用到python,filesystem,system,regex,thread等components 169 find_package( Boost REQUIRED COMPONENTS python filesystem system regex thread ) 170 else() 171 # 使用自己的Boost 172 set( Boost_INCLUDE_DIR ${BoostParts_SOURCE_DIR} ) 173 set( Boost_LIBRARIES BoostParts ) 174 endif() 175 176 # 相关头文件的加入,Boost,Python和Clang 177 include_directories( 178 SYSTEM 179 ${Boost_INCLUDE_DIR} 180 ${PYTHON_INCLUDE_DIRS} 181 ${CLANG_INCLUDES_DIR} 182 ) 183 184 # 全局递归查找h,cpp文件给SERVER_SOURCES 185 file( GLOB_RECURSE SERVER_SOURCES *.h *.cpp ) 186 187 # 全局递归查找测试相关文件 188 file( GLOB_RECURSE to_remove tests/*.h tests/*.cpp CMakeFiles/*.cpp *client* ) 189 190 if( to_remove ) 191 # 学习list相关的REMOVE_ITEM命令 192 list( REMOVE_ITEM SERVER_SOURCES ${to_remove} ) 193 endif() 194 195 # 这里就是这个变量最关键的地方,它会去include并且开启宏 196 if ( USE_CLANG_COMPLETER ) 197 include_directories( 198 ${CMAKE_CURRENT_SOURCE_DIR} 199 "${CMAKE_CURRENT_SOURCE_DIR}/ClangCompleter" ) 200 add_definitions( -DUSE_CLANG_COMPLETER ) 201 else() 202 # 否则的话寻找所有ClangCompleter下的头和源文件进行删除 203 file( GLOB_RECURSE to_remove_clang ClangCompleter/*.h ClangCompleter/*.cpp ) 204 205 if( to_remove_clang ) 206 list( REMOVE_ITEM SERVER_SOURCES ${to_remove_clang} ) 207 endif() 208 endif() 209 210 # 如果用户使用额外的libclang或者使用系统自带的libclang 211 if ( EXTERNAL_LIBCLANG_PATH OR USE_SYSTEM_LIBCLANG ) 212 if ( USE_SYSTEM_LIBCLANG ) # 如果是系统自带 213 if ( APPLE ) 214 set( ENV_LIB_PATHS ENV DYLD_LIBRARY_PATH ) # 将环境变量下的DYLD_LIBRARY_PATH给ENV_LIB_PATHS 215 elseif ( UNIX ) 216 set( ENV_LIB_PATHS ENV LD_LIBRARY_PATH ) # 这也是我之前讲的一定要把你编译的libclang加入到这个环境变量中,因为它会根据这个去寻找 217 elseif ( WIN32 ) 218 set( ENV_LIB_PATHS ENV PATH ) 219 else () 220 set( ENV_LIB_PATHS "" ) 221 endif() 222 file( GLOB SYS_LLVM_PATHS "/usr/lib/llvm*/lib" ) 223 224 # 进行相关的libclang查找,在你之前给它指定的环境变量中 225 find_library( TEMP clang 226 PATHS 227 ${ENV_LIB_PATHS} 228 /usr/lib 229 /usr/lib/llvm 230 ${SYS_LLVM_PATHS} 231 /Library/Developer/CommandLineTools/usr/lib, 232 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib ) 233 # 将寻找到的变量给EXTERNAL_LIBCLANG_PATH 234 set( EXTERNAL_LIBCLANG_PATH ${TEMP} ) 235 else() 236 if ( NOT APPLE ) 237 # 设置相关rpath 238 set( CMAKE_BUILD_WITH_INSTALL_RPATH TRUE ) 239 # 设置make install之后的rpath 240 set( CMAKE_INSTALL_RPATH "\$ORIGIN" ) 241 endif() 242 endif() 243 244 set( LIBCLANG_TARGET "" ) 245 message( 246 "Using external libclang: ${EXTERNAL_LIBCLANG_PATH}" ) 247 message("libclang_target is ${LIBCLANG_TARGET}") 248 else() 249 set( LIBCLANG_TARGET ) 250 endif() 251 252 # 如果有额外的rpath,在这里进行设置 253 if ( EXTRA_RPATH ) 254 set( CMAKE_INSTALL_RPATH "${EXTRA_RPATH}:${CMAKE_INSTALL_RPATH}" ) 255 endif() 256 257 # 如果在Linux下需要额外的rt库 258 if ( UNIX AND NOT APPLE ) 259 set( EXTRA_LIBS rt ) 260 endif() 261 262 # 将目录下所有的h和cpp给CLIENT_SOURCES 263 file( GLOB CLIENT_SOURCES *.h *.cpp ) 264 # 相应SERVER_SPECIFIC赋值 265 file( GLOB SERVER_SPECIFIC *ycm_core* ) 266 267 if( SERVER_SPECIFIC ) 268 # 移除相关CLIEN_SOURCES下的SERVER_SPECIFIC 269 list( REMOVE_ITEM CLIENT_SOURCES ${SERVER_SPECIFIC} ) 270 endif() 271 272 # 创建client的library,并且是动态库 273 add_library( ${CLIENT_LIB} SHARED 274 ${CLIENT_SOURCES} 275 ) 276 277 # 将这个库与之前的rt,Boost,Python进行链接 278 target_link_libraries( ${CLIENT_LIB} 279 ${Boost_LIBRARIES} 280 ${PYTHON_LIBRARIES} 281 ${EXTRA_LIBS} 282 ) 283 284 # 创建server的library,并且是动态库 285 add_library( ${SERVER_LIB} SHARED 286 ${SERVER_SOURCES} 287 ) 288 289 # 将这个库与之前的rt,Boost,Python,libclang,而外的server lib进行链接 290 target_link_libraries( ${SERVER_LIB} 291 ${Boost_LIBRARIES} 292 ${PYTHON_LIBRARIES} 293 ${LIBCLANG_TARGET} 294 ${EXTRA_LIBS} 295 ) 296 297 # 如果定义了LIBCLANG_TARGET 298 if( LIBCLANG_TARGET ) 299 if( NOT WIN32 ) 300 # 在非WIN32情况下增加自定义命令,将libclang.so/dll拷贝到自己目录下 301 add_custom_command( 302 TARGET ${SERVER_LIB} 303 POST_BUILD 304 COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$<TARGET_FILE_DIR:${SERVER_LIB}>" 305 ) 306 else() 307 add_custom_command( 308 TARGET ${SERVER_LIB} 309 POST_BUILD 310 COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$<TARGET_FILE_DIR:${SERVER_LIB}>") 311 endif() 312 endif() 313 314 # 建立依赖关系,表示这个项目需要这两个库共同完成 315 add_custom_target( ${PROJECT_NAME} 316 DEPENDS ${CLIENT_LIB} ${SERVER_LIB} ) 317 318 # Mac下的相关设置,如果是利用rpath的话Mac下还是会去寻找系统库,即使用户显示指定 319 # 这里需要改用@loader_path 320 if ( EXTERNAL_LIBCLANG_PATH AND APPLE ) 321 add_custom_command( TARGET ${SERVER_LIB} 322 POST_BUILD 323 COMMAND install_name_tool 324 "-change" 325 "@rpath/libclang.dylib" 326 "@loader_path/libclang.dylib" 327 "$<TARGET_FILE:${SERVER_LIB}>" 328 ) 329 endif() 330 331 # 将这些库的前缀lib去掉,因为会扰乱Python模块的查找 332 set_target_properties( ${CLIENT_LIB} PROPERTIES PREFIX "") 333 set_target_properties( ${SERVER_LIB} PROPERTIES PREFIX "") 334 335 if ( WIN32 OR CYGWIN ) 336 # 进行Windows下相关库的转移存放 337 set_target_properties( ${CLIENT_LIB} PROPERTIES 338 RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) 339 set_target_properties( ${SERVER_LIB} PROPERTIES 340 RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) 341 foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) 342 string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) 343 set_target_properties( ${CLIENT_LIB} PROPERTIES 344 RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/../.. ) 345 set_target_properties( ${SERVER_LIB} PROPERTIES 346 RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/../.. ) 347 endforeach() 348 349 if ( WIN32 ) 350 # 建立后缀名.pyd 351 set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".pyd") 352 set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".pyd") 353 elseif ( CYGWIN ) 354 # CYGIN下后缀为dll 355 set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".dll") 356 set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".dll") 357 endif() 358 else() 359 # Mac和Linux下都为.so,虽然Mac下应该默认为.dylib,但Python识别不了dylib,因此这里还是设置成.so 360 set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".so") 361 set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".so") 362 endif() 363 364 # 设置相关lib的输出目录 365 set_target_properties( ${CLIENT_LIB} PROPERTIES 366 LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) 367 set_target_properties( ${SERVER_LIB} PROPERTIES 368 LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) 369 370 if ( USE_DEV_FLAGS AND ( CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG ) AND 371 NOT CMAKE_GENERATOR_IS_XCODE ) 372 # 增加相应flag 373 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror" ) 374 endif() 375 376 # 提出警告在使用C++11特性下 377 if ( USE_DEV_FLAGS AND COMPILER_IS_CLANG AND NOT CMAKE_GENERATOR_IS_XCODE AND 378 NOT SYSTEM_IS_FREEBSD ) 379 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wc++98-compat" ) 380 endif() 381 382 if( SYSTEM_IS_SUNOS ) 383 # SunOS需要-pthreads这个flag才能正常使用 384 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads" ) 385 endif() 386 387 # 增加测试子目录tests 388 add_subdirectory( tests )