標籤:

使用 CMake 不用路徑地調用 libclang

使用 CMake 不用路徑地調用 libclang

來自專欄 千里冰封被吊打的日常

博客原文:

ice1000.org?

ice1000.org

作為一個有高尚的情操的程序員,應該學會在寫非練手項目的時候構建流程不使用與本機路徑相關的依賴,這樣把代碼往 CI 上部署、在公司與個人電腦間傳輸、或者給別人用的時候都方便的多。

如果有 gradle, pip, gem, maven, sbt 這類基於包的構建工具的話,這根本就不是問題。別人寫的依賴可以在遠端調用,自己寫的依賴也可以本地調用。

如果有 npm, go get 這類基於版本控制的構建工具的話,這當作也只有一個小問題,就是項目維護者腦抽了一下,可能就會誤傷在此期間構建程序的程序員。

不過至少他們都能自主尋找包並下載安裝,只要包是正確的就能成功安裝,安裝的配置是由包的開發者提供的。

CMake 作為全宇宙 用戶量 / 構建工具本身的質量 最高的構建工具,要做到這一點是很困難的 —— CMake 只有在『沒有需要導入的依賴』或者『所有依賴都是系統自帶的(比如在 Windows 上調用 DirectX)』情況下能做到『無痛使用』。 一旦需要導入一些外部依賴(比如 glfw3,比如 jni),就需要用到 find_package 這個函數,which 只有滿足這兩個條件的時候才能成功執行:

  • CMake 能找到 [package名]Config.cmake 或者 [package名]-config.cmake 中的一個文件
  • CMake 能找到 Find[package名].cmake 這個文件,並且這個文件里的代碼能 find 到這個 package

之前在寫 JNI 代碼的時候需要使用 CMake 尋找 jni.h 等一系列文件,於是只需要這麼一小段代碼就可以把他們加進 CMake 編譯時的 classpath:

find_package(Java REQUIRED)find_package(JNI REQUIRED)

然後我們可以在找到之後輸出一下找到的 JDK 的路徑:

if (JNI_FOUND) message(STATUS "JNI_INCLUDE_DIRS=${JNI_INCLUDE_DIRS}") message(STATUS "JNI_LIBRARIES=${JNI_LIBRARIES}")endif ()

但如果我們要調用 libclang 的時候:

find_package(LibClang REQUIRED)

它就會說找不到 LibClangConfig.cmake, LibClang-config.cmake, FindLibClang.cmake 中的一個。

首先確保已經安裝 clang:

$ sudo apt install clang-dev

在這個時候我已經上 Google/StackOverflow 找了一遍了,由於 clang 這個名詞的特殊性(不僅是個包,還是個 C++ 編譯器),搜索 clang + cmake 都是教你怎麼配置 CMake 使 CMake 調用 clang 而不是 gcc 的,搜索 libclang + cmake 得到的很多結果需要配置環境變數,把 clang/llvm 的安裝路徑指定好。 而我的 clang 明明就在 PATH 里,憑什麼需要另外配置呢。

那麼我就來自己想辦法。

前兩者應該是安裝包的時候就自帶了的,既然 clang 沒有,那麼只能去找個 FindLibClang.cmake 啦。

搜索 FindLibClang.cmake ,找到了一個 Emacs 插件 rtags 的一個 CMake 模塊(注意這個代碼是 GPLv3 的哦):

if (NOT LIBCLANG_ROOT_DIR) set(LIBCLANG_ROOT_DIR $ENV{LIBCLANG_ROOT_DIR})endif ()if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) set(LIBCLANG_LLVM_CONFIG_EXECUTABLE $ENV{LIBCLANG_LLVM_CONFIG_EXECUTABLE}) if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) if (APPLE) foreach(major RANGE 9 3) foreach(minor RANGE 9 0) foreach(patch RANGE 9 0) message(STATUS "trying llvm-config llvm-config${major}${minor} in /usr/local/Cellar/llvm/${major}.${minor}.${patch}/bin") find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE NAMES llvm-config llvm-config${major}${minor} llvm-config-${major}${minor} llvm-config-${major} llvm-config${major} PATHS /usr/local/Cellar/llvm/${major}.${minor}.${patch}/bin) if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) break() endif () endforeach () if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) break() endif () endforeach () if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) break() endif () endforeach () else () set(llvm_config_names llvm-config) foreach(major RANGE 9 3) list(APPEND llvm_config_names "llvm-config${major}" "llvm-config-${major}") foreach(minor RANGE 9 0) list(APPEND llvm_config_names "llvm-config${major}${minor}" "llvm-config-${major}.${minor}" "llvm-config-mp-${major}.${minor}") endforeach () endforeach () find_program(LIBCLANG_LLVM_CONFIG_EXECUTABLE NAMES ${llvm_config_names}) endif () endif () if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) message(STATUS "llvm-config executable found: ${LIBCLANG_LLVM_CONFIG_EXECUTABLE}") endif ()endif ()if (NOT LIBCLANG_CXXFLAGS) if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) message(FATAL_ERROR "Could NOT find llvm-config executable and LIBCLANG_CXXFLAGS is not set ") endif () execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE LIBCLANG_CXXFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) if (NOT LIBCLANG_CXXFLAGS) find_path(LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT clang-c/Index.h HINTS ${LIBCLANG_ROOT_DIR}/include NO_DEFAULT_PATH) if (NOT EXISTS ${LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}) find_path(LIBCLANG_CXXFLAGS clang-c/Index.h) if (NOT EXISTS ${LIBCLANG_CXXFLAGS}) message(FATAL_ERROR "Could NOT find clang include path. You can fix this by setting LIBCLANG_CXXFLAGS in your shell or as a cmake variable.") endif () else () set(LIBCLANG_CXXFLAGS ${LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}) endif () set(LIBCLANG_CXXFLAGS "-I${LIBCLANG_CXXFLAGS}") endif () string(REGEX MATCHALL "-(D__?[a-zA-Z_]*|I([^" ]+|"[^"]+"))" LIBCLANG_CXXFLAGS "${LIBCLANG_CXXFLAGS}") string(REGEX REPLACE ";" " " LIBCLANG_CXXFLAGS "${LIBCLANG_CXXFLAGS}") set(LIBCLANG_CXXFLAGS ${LIBCLANG_CXXFLAGS} CACHE STRING "The LLVM C++ compiler flags needed to compile LLVM based applications.") unset(LIBCLANG_CXXFLAGS_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT CACHE)endif ()if (NOT EXISTS ${LIBCLANG_LIBDIR}) if (NOT LIBCLANG_LLVM_CONFIG_EXECUTABLE) message(FATAL_ERROR "Could NOT find llvm-config executable and LIBCLANG_LIBDIR is not set ") endif () execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --libdir OUTPUT_VARIABLE LIBCLANG_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE) if (NOT EXISTS ${LIBCLANG_LIBDIR}) message(FATAL_ERROR "Could NOT find clang libdir. You can fix this by setting LIBCLANG_LIBDIR in your shell or as a cmake variable.") endif () set(LIBCLANG_LIBDIR ${LIBCLANG_LIBDIR} CACHE STRING "Path to the clang library.")endif ()if (NOT LIBCLANG_LIBRARIES) find_library(LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT NAMES clang libclang HINTS ${LIBCLANG_LIBDIR} ${LIBCLANG_ROOT_DIR}/lib NO_DEFAULT_PATH) if (LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT) set(LIBCLANG_LIBRARIES "${LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT}") else () find_library(LIBCLANG_LIBRARIES NAMES clang libclang) if (NOT EXISTS ${LIBCLANG_LIBRARIES}) set (LIBCLANG_LIBRARIES "-L${LIBCLANG_LIBDIR}" "-lclang" "-Wl,-rpath,${LIBCLANG_LIBDIR}") endif () endif () unset(LIBCLANG_LIB_HACK_CMAKECACHE_DOT_TEXT_BULLSHIT CACHE)endif ()set(LIBCLANG_LIBRARY ${LIBCLANG_LIBRARIES} CACHE FILEPATH "Path to the libclang library")if (LIBCLANG_LLVM_CONFIG_EXECUTABLE) execute_process(COMMAND ${LIBCLANG_LLVM_CONFIG_EXECUTABLE} --version OUTPUT_VARIABLE LIBCLANG_VERSION_STRING OUTPUT_STRIP_TRAILING_WHITESPACE)else () set(LIBCLANG_VERSION_STRING "Unknown")endif ()message("-- Using Clang version ${LIBCLANG_VERSION_STRING} from ${LIBCLANG_LIBDIR} with CXXFLAGS ${LIBCLANG_CXXFLAGS}")# Handly the QUIETLY and REQUIRED arguments and set LIBCLANG_FOUND to TRUE if all listed variables are TRUEinclude(FindPackageHandleStandardArgs)find_package_handle_standard_args(LibClang DEFAULT_MSG LIBCLANG_LIBRARY LIBCLANG_CXXFLAGS LIBCLANG_LIBDIR)mark_as_advanced(LIBCLANG_CXXFLAGS LIBCLANG_LIBRARY LIBCLANG_LLVM_CONFIG_EXECUTABLE LIBCLANG_LIBDIR)

其實我中間找到過兩三個類似的,但那幾個 FindLibClang 都寫的不穩,在我的電腦上都不 work ,需要環境變數。 而看這個實現,它是藉助 llvm-config 這個程序(就是配置 llvm 環境的時候的標配啦,一般是 c++ $(llvm-config --cxxflags) main.cpp 這樣用的)查找的 clang 配置,穩如老狗。

根據它在注釋里寫的:

# FindLibClang## This module searches libclang and llvm-config, the llvm-config tool is used to# get information about the installed llvm/clang package to compile LLVM based# programs.## It defines the following variables## ``LIBCLANG_LLVM_CONFIG_EXECUTABLE``# the llvm-config tool to get various information.# ``LIBCLANG_LIBRARIES``# the clang libraries to link against to use Clang/LLVM.# ``LIBCLANG_LIBDIR``# the directory where the clang libraries are located.# ``LIBCLANG_FOUND``# true if libclang was found# ``LIBCLANG_VERSION_STRING``# version number as a string# ``LIBCLANG_CXXFLAGS``# the compiler flags for files that include LLVM headers

生成的變數都寫清楚了,所以我們就可以很容易地導入它了。 在我們自己項目里,先把上面的 FindLibClang.cmake 放進 項目根目錄/cmake-modules/ 目錄里(其實就是隨便一個目錄),然後在 CMakeLists.txt 里寫:

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake-modules/")find_package(LibClang REQUIRED)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LIBCLANG_CXXFLAGS}")

就可以讓 CMake 找到 clang 的頭文件啦。

然後就可以在 CLion 裡面寫這樣的代碼了:

#include <clang/AST/ASTConsumer.h>#include <clang/AST/RecursiveASTVisitor.h>#include <clang/Frontend/CompilerInstance.h>#include <clang/Frontend/FrontendAction.h>#include <clang/Tooling/Tooling.h>

可以正確地 navigate 到對應的文件,自動生成 override 之類的特性都可以用了。

class FindNamedClassAction : public clang::ASTFrontendAction {protected: std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &CI, StringRef InFile) override { return std::unique_ptr<clang::ASTConsumer>(new FindNamedClassASTConsumer(CI.getASTContext())); }};auto main(int argc, const char **const argv) -> int { if (argc > 1) clang::tooling::runToolOnCode(new FindNamedClassAction, argv[1]); return 0;}

目前我還沒找到如何正確編譯和鏈接程序的方法,這個代碼會導致 unresolved reference:

target_link_libraries(clang_test ${LIBCLANG_LIBRARIES})

網上也找不到解決方案,只能自己腦補程序運行的樣子。


推薦閱讀:

解決 Windows 下 Python 安裝 Dlib 的問題:Cmake 找不到 boost
關於OpenCV的一次Debug
Ubuntu下邊用邊學CMAKE(一)
macOS Sierra10.12.6下安裝OpenCV3.3.0

TAG:GCC | CMake | Clang |