個人一直有一個想法,就是想出一系列關於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安裝都沒有任何問題。下面正式進入正題
# CMake要求的最低版本號 cmake_minimum_required( VERSION 2.8 ) # 項目名稱 project( ycm_support_libs ) # 設置客戶端lib和服務端lib的變量名稱 set( CLIENT_LIB "ycm_client_support" ) set( SERVER_LIB "ycm_core" ) # 設置Python的版本號變量 set( Python_ADDITIONAL_VERSIONS 2.7 2.6 ) # 進行Python包的查找,這里是REQUIRED表示必須 find_package( PythonLibs 2.6 REQUIRED ) # 如果Python版本號低於3.0.0就進行FATAL_ERROR的出錯信息 if ( NOT PYTHONLIBS_VERSION_STRING VERSION_LESS "3.0.0" ) message( FATAL_ERROR "CMake found python3 libs instead of python2 libs. YCM works only with " "python2.\n" ) endif() # 各種option option( USE_DEV_FLAGS "Use compilation flags meant for YCM developers" OFF ) # 這個變量就是我上文講到的很關鍵的一個變量,來判斷當前用戶需要不需要libclang option( USE_CLANG_COMPLETER "Use Clang semantic completer for C/C++/ObjC" OFF ) # install.sh中的--system-libclang就與這個變量進行交互 option( USE_SYSTEM_LIBCLANG "Set to ON to use the system libclang library" OFF ) # YCM作者推薦的用法,在這里直接寫入Clang的相關路徑 注意這里的CACHE PATH,表示當用戶如果命令行 # 進行指定,那優先會去讀用戶的命令行,而不是用這里的set,並且把相關的值寫入Cache中 set( PATH_TO_LLVM_ROOT "" CACHE PATH "Path to the root of a LLVM+Clang binary distribution" ) # YCM作者推薦的另外一種安裝方法,直接將libclang.so全路徑寫死 set( EXTERNAL_LIBCLANG_PATH "" CACHE PATH "Path to the libclang library to use" ) # 如果你使用libclang但是沒有指定用不用系統的libclang,沒有指定llvm_root,沒有指定額外的libclang.so,那么就會帶你去下載 if ( USE_CLANG_COMPLETER AND NOT USE_SYSTEM_LIBCLANG AND NOT PATH_TO_LLVM_ROOT AND NOT EXTERNAL_LIBCLANG_PATH ) message( "Downloading Clang 3.4" ) # 這就是llvm官網的3.4下載路徑 set( CLANG_URL "http://llvm.org/releases/3.4" ) # 如果當前客戶端是蘋果 Mac OS X if ( APPLE ) # 設置Clang的文件夾名稱 set( CLANG_DIRNAME "clang+llvm-3.4-x86_64-apple-darwin10.9" ) # 設置Clang的MD5校驗碼 set( CLANG_MD5 "4f43ea0e87090ae5e7bec12373ca4927" ) # 設置Clang文件名稱為之后加上tar.gz set( CLANG_FILENAME "${CLANG_DIRNAME}.tar.gz" ) else() # 如果是64位平台 if ( 64_BIT_PLATFORM ) # 設置Clang的文件夾名稱 set( CLANG_DIRNAME "clang+llvm-3.4-x86_64-unknown-ubuntu12.04" ) # 設置Clang的MD5校驗碼 set( CLANG_MD5 "6077459d20a7ff412eefc6ce3b9f5c85" ) # 設置Clang文件名稱為之后加上tar.gz set( CLANG_FILENAME "${CLANG_DIRNAME}.tar.xz" ) else() # 表示此時為32位的Linux,下載3.3版本 message( "No pre-built Clang 3.4 binaries for 32 bit linux, " "downloading Clang 3.3" ) set( CLANG_URL "http://llvm.org/releases/3.3" ) # 設置Clang的文件夾名稱 set( CLANG_DIRNAME "clang+llvm-3.3-i386-debian6" ) # 設置Clang的MD5校驗碼 set( CLANG_MD5 "415d033b60659433d4631df894673802" ) # 設置Clang文件名稱為之后加上tar.gz set( CLANG_FILENAME "${CLANG_DIRNAME}.tar.bz2" ) endif() endif() # 下載命令 file( DOWNLOAD "${CLANG_URL}/${CLANG_FILENAME}" "./${CLANG_FILENAME}" SHOW_PROGRESS EXPECTED_MD5 "${CLANG_MD5}" ) # 文件名正則表達式匹配,進行相應的解壓 if ( CLANG_FILENAME MATCHES ".+bz2" ) # 執行相關的外部命令 tar execute_process( COMMAND tar -xjf ${CLANG_FILENAME} ) elseif( CLANG_FILENAME MATCHES ".+xz" ) execute_process( COMMAND tar -xJf ${CLANG_FILENAME} ) else() execute_process( COMMAND tar -xzf ${CLANG_FILENAME} ) endif() # 設置PATH_TO_LLVM_ROOT的路徑為當前CMake二進制路徑下的Clang目錄 set( PATH_TO_LLVM_ROOT "${CMAKE_CURRENT_BINARY_DIR}/../${CLANG_DIRNAME}" ) endif() # 如果設置了PATH_TO_LLVM_ROOT或者用戶使用系統libclang或者有額外的libclang,就開啟USE_CLANG_COMPLETER # 這個變量我上文提過,很關鍵 if ( PATH_TO_LLVM_ROOT OR USE_SYSTEM_LIBCLANG OR EXTERNAL_LIBCLANG_PATH ) set( USE_CLANG_COMPLETER TRUE ) endif() # 開始使用這個變量,如果用戶確定使用libclang,但是沒有root沒有系統clang沒有額外clang,那么 # 進行錯誤性提示 if ( USE_CLANG_COMPLETER AND NOT PATH_TO_LLVM_ROOT AND NOT USE_SYSTEM_LIBCLANG AND NOT EXTERNAL_LIBCLANG_PATH ) message( FATAL_ERROR "You have not specified which libclang to use. You have several options:\n" " 1. Set PATH_TO_LLVM_ROOT to a path to the root of a LLVM+Clang binary " "distribution. You can download such a binary distro from llvm.org. This " "is the recommended approach.\n" " 2. Set USE_SYSTEM_LIBCLANG to ON; this makes YCM search for the system " "version of libclang.\n" " 3. Set EXTERNAL_LIBCLANG_PATH to a path to whatever " "libclang.[so|dylib|dll] you wish to use.\n" "You HAVE to pick one option. See the docs for more information.") endif() # 進行用戶提醒,提醒用戶當前是否使用libclang if ( USE_CLANG_COMPLETER ) message( "Using libclang to provide semantic completion for C/C++/ObjC" ) else() message( "NOT using libclang, no semantic completion for C/C++/ObjC will be " "available" ) endif() # 如果設置了root就設置CLANG_INCLUDES_DIR為root下的include # 否則CLANG_INCLUDES_DIR為CMake下llvm/include if ( PATH_TO_LLVM_ROOT ) set( CLANG_INCLUDES_DIR "${PATH_TO_LLVM_ROOT}/include" ) else() set( CLANG_INCLUDES_DIR "${CMAKE_SOURCE_DIR}/llvm/include" ) endif() # 如果當前的include路徑不是絕對路徑 if ( NOT IS_ABSOLUTE "${CLANG_INCLUDES_DIR}" ) # 設置它為絕對路徑 get_filename_component(CLANG_INCLUDES_DIR "${CMAKE_BINARY_DIR}/${CLANG_INCLUDES_DIR}" ABSOLUTE) endif() # 如果沒有額外的libclang,但是有root if ( NOT EXTERNAL_LIBCLANG_PATH AND PATH_TO_LLVM_ROOT ) if ( MINGW ) # 如果是MINGW # 設置libclang的尋找路徑(后面的find_library會去尋找) set( LIBCLANG_SEARCH_PATH "${PATH_TO_LLVM_ROOT}/bin" ) else() set( LIBCLANG_SEARCH_PATH "${PATH_TO_LLVM_ROOT}/lib" ) endif() # 這里TEMP會被find_library去尋找clang,和libclang兩個so,並且復制路徑給TEMP find_library( TEMP NAMES clang libclang PATHS ${LIBCLANG_SEARCH_PATH} NO_DEFAULT_PATH ) message("temp is ${TEMP}") # 設置額外的libclang為這個路徑 set( EXTERNAL_LIBCLANG_PATH ${TEMP} ) endif() # 如果當前為蘋果,設置額外的flag if ( APPLE ) set( CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem " ) endif() # 如果用戶使用系統自帶的boost if ( USE_SYSTEM_BOOST ) # 進行find boost命令,會用到python,filesystem,system,regex,thread等components find_package( Boost REQUIRED COMPONENTS python filesystem system regex thread ) else() # 使用自己的Boost set( Boost_INCLUDE_DIR ${BoostParts_SOURCE_DIR} ) set( Boost_LIBRARIES BoostParts ) endif() # 相關頭文件的加入,Boost,Python和Clang include_directories( SYSTEM ${Boost_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS} ${CLANG_INCLUDES_DIR} ) # 全局遞歸查找h,cpp文件給SERVER_SOURCES file( GLOB_RECURSE SERVER_SOURCES *.h *.cpp ) # 全局遞歸查找測試相關文件 file( GLOB_RECURSE to_remove tests/*.h tests/*.cpp CMakeFiles/*.cpp *client* )
if( to_remove )
# 學習list相關的REMOVE_ITEM命令
list( REMOVE_ITEM SERVER_SOURCES ${to_remove} )
endif()
# 這里就是這個變量最關鍵的地方,它會去include並且開啟宏
if ( USE_CLANG_COMPLETER )
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
"${CMAKE_CURRENT_SOURCE_DIR}/ClangCompleter" )
add_definitions( -DUSE_CLANG_COMPLETER )
else()
# 否則的話尋找所有ClangCompleter下的頭和源文件進行刪除
file( GLOB_RECURSE to_remove_clang ClangCompleter/*.h ClangCompleter/*.cpp )
if( to_remove_clang )
list( REMOVE_ITEM SERVER_SOURCES ${to_remove_clang} )
endif()
endif()
# 如果用戶使用額外的libclang或者使用系統自帶的libclang
if ( EXTERNAL_LIBCLANG_PATH OR USE_SYSTEM_LIBCLANG )
if ( USE_SYSTEM_LIBCLANG ) # 如果是系統自帶
if ( APPLE )
set( ENV_LIB_PATHS ENV DYLD_LIBRARY_PATH ) # 將環境變量下的DYLD_LIBRARY_PATH給ENV_LIB_PATHS
elseif ( UNIX )
set( ENV_LIB_PATHS ENV LD_LIBRARY_PATH ) # 這也是我之前講的一定要把你編譯的libclang加入到這個環境變量中,因為它會根據這個去尋找
elseif ( WIN32 )
set( ENV_LIB_PATHS ENV PATH )
else ()
set( ENV_LIB_PATHS "" )
endif()
file( GLOB SYS_LLVM_PATHS "/usr/lib/llvm*/lib" )
# 進行相關的libclang查找,在你之前給它指定的環境變量中 find_library( TEMP clang PATHS ${ENV_LIB_PATHS} /usr/lib /usr/lib/llvm ${SYS_LLVM_PATHS} /Library/Developer/CommandLineTools/usr/lib, /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib ) # 將尋找到的變量給EXTERNAL_LIBCLANG_PATH set( EXTERNAL_LIBCLANG_PATH ${TEMP} ) else() if ( NOT APPLE ) # 設置相關rpath set( CMAKE_BUILD_WITH_INSTALL_RPATH TRUE ) # 設置make install之后的rpath set( CMAKE_INSTALL_RPATH "\$ORIGIN" ) endif() endif() set( LIBCLANG_TARGET "" ) message( "Using external libclang: ${EXTERNAL_LIBCLANG_PATH}" ) message("libclang_target is ${LIBCLANG_TARGET}") else() set( LIBCLANG_TARGET ) endif() # 如果有額外的rpath,在這里進行設置 if ( EXTRA_RPATH ) set( CMAKE_INSTALL_RPATH "${EXTRA_RPATH}:${CMAKE_INSTALL_RPATH}" ) endif() # 如果在Linux下需要額外的rt庫 if ( UNIX AND NOT APPLE ) set( EXTRA_LIBS rt ) endif() # 將目錄下所有的h和cpp給CLIENT_SOURCES file( GLOB CLIENT_SOURCES *.h *.cpp ) # 相應SERVER_SPECIFIC賦值 file( GLOB SERVER_SPECIFIC *ycm_core* ) if( SERVER_SPECIFIC ) # 移除相關CLIEN_SOURCES下的SERVER_SPECIFIC list( REMOVE_ITEM CLIENT_SOURCES ${SERVER_SPECIFIC} ) endif() # 創建client的library,並且是動態庫 add_library( ${CLIENT_LIB} SHARED ${CLIENT_SOURCES} ) # 將這個庫與之前的rt,Boost,Python進行鏈接 target_link_libraries( ${CLIENT_LIB} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ${EXTRA_LIBS} ) # 創建server的library,並且是動態庫 add_library( ${SERVER_LIB} SHARED ${SERVER_SOURCES} ) # 將這個庫與之前的rt,Boost,Python,libclang,而外的server lib進行鏈接 target_link_libraries( ${SERVER_LIB} ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} ${LIBCLANG_TARGET} ${EXTRA_LIBS} ) # 如果定義了LIBCLANG_TARGET if( LIBCLANG_TARGET ) if( NOT WIN32 ) # 在非WIN32情況下增加自定義命令,將libclang.so/dll拷貝到自己目錄下 add_custom_command( TARGET ${SERVER_LIB} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${LIBCLANG_TARGET}" "$<TARGET_FILE_DIR:${SERVER_LIB}>" ) else() add_custom_command( TARGET ${SERVER_LIB} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${PATH_TO_LLVM_ROOT}/bin/libclang.dll" "$<TARGET_FILE_DIR:${SERVER_LIB}>") endif() endif() # 建立依賴關系,表示這個項目需要這兩個庫共同完成 add_custom_target( ${PROJECT_NAME} DEPENDS ${CLIENT_LIB} ${SERVER_LIB} ) # Mac下的相關設置,如果是利用rpath的話Mac下還是會去尋找系統庫,即使用戶顯示指定 # 這里需要改用@loader_path if ( EXTERNAL_LIBCLANG_PATH AND APPLE ) add_custom_command( TARGET ${SERVER_LIB} POST_BUILD COMMAND install_name_tool "-change" "@rpath/libclang.dylib" "@loader_path/libclang.dylib" "$<TARGET_FILE:${SERVER_LIB}>" ) endif() # 將這些庫的前綴lib去掉,因為會擾亂Python模塊的查找 set_target_properties( ${CLIENT_LIB} PROPERTIES PREFIX "") set_target_properties( ${SERVER_LIB} PROPERTIES PREFIX "") if ( WIN32 OR CYGWIN ) # 進行Windows下相關庫的轉移存放 set_target_properties( ${CLIENT_LIB} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) set_target_properties( ${SERVER_LIB} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG ) set_target_properties( ${CLIENT_LIB} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/../.. ) set_target_properties( ${SERVER_LIB} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${PROJECT_SOURCE_DIR}/../.. ) endforeach() if ( WIN32 ) # 建立后綴名.pyd set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".pyd") set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".pyd") elseif ( CYGWIN ) # CYGIN下后綴為dll set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".dll") set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".dll") endif() else() # Mac和Linux下都為.so,雖然Mac下應該默認為.dylib,但Python識別不了dylib,因此這里還是設置成.so set_target_properties( ${CLIENT_LIB} PROPERTIES SUFFIX ".so") set_target_properties( ${SERVER_LIB} PROPERTIES SUFFIX ".so") endif() # 設置相關lib的輸出目錄 set_target_properties( ${CLIENT_LIB} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) set_target_properties( ${SERVER_LIB} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../.. ) if ( USE_DEV_FLAGS AND ( CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG ) AND NOT CMAKE_GENERATOR_IS_XCODE ) # 增加相應flag set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror" ) endif() # 提出警告在使用C++11特性下 if ( USE_DEV_FLAGS AND COMPILER_IS_CLANG AND NOT CMAKE_GENERATOR_IS_XCODE AND NOT SYSTEM_IS_FREEBSD ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wc++98-compat" ) endif() if( SYSTEM_IS_SUNOS ) # SunOS需要-pthreads這個flag才能正常使用 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads" ) endif()
# 增加測試子目錄tests add_subdirectory( tests )