引言

Docker是非常方便使用的虚拟容器技术,为自己的项目或程序提供一个Dockerfile文件可以快速的建立对应的开发或部署环境,减少因为环境或工具链缺失而痛苦、漫长的前期准备工作。

LLVM是备受瞩目的编译器项目,提供了一系列工具链,但要将其引入自己的代码中进行编译的过程却也不是那么容易,甚至会导致很多人放弃尝试。

这篇通过Dockerfile建立一个开发环境,然后在这个环境中通过CMake配置以顺利使用LLVM工具链完成项目构建,以后想继续学习或使用LLVM就可以在这个环境中拿这个模板直接操作了。

Dockerfile文件

这里使用的是LLVM-13,选择了Debian-11.6这个操作系统镜像为基础:

# 基础镜像
FROM debian:11.6

LABEL MAINTAINER lesliezhu http://lesliezhu.com

# 换成阿里云软件源
RUN echo \
    deb http://mirrors.aliyun.com/debian/ bullseye main non-free contrib\
    deb-src http://mirrors.aliyun.com/debian/ bullseye main non-free contrib\
    deb http://mirrors.aliyun.com/debian-security bullseye/updates main\
    deb-src http://mirrors.aliyun.com/debian-security bullseye/updates main\
    deb http://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib\
    deb-src http://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib\
    deb http://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib\
    deb-src http://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib\
    deb http://mirrors.ustc.edu.cn/debian-security/ stable-security main non-free contrib\
    deb-src http://mirrors.ustc.edu.cn/debian-security/ stable-security main non-free contrib\
    > /etc/apt/sources.list

# 安装软件包
RUN apt-get -y update && \
    apt-get install -y cmake && \
    apt-get install -y clang-13 llvm-13 llvm-13-dev  llvm-13-tools llvm-13-runtime llvm-13-linker-tools && \
    apt-get install -y libgflags-dev libgtest-dev && \
    apt-get install -y libboost-dev libboost-serialization-dev && \
    apt-get install -y libreadline-dev zlib1g zlib1g-dev


# 设置初始化环境变量等
RUN echo '\n\n# for LLVM-13 \n\
export CC=/usr/bin/clang-13 \n\
export CXX=/usr/bin/clang++-13 \n\
ln -sf /usr/bin/llvm-config-13 /usr/bin/llvm-config \n'\
    >> ~/.bashrc

构建Docker镜像环境

# 构建镜像
$ docker build -f Dockerfile -t ledge:builder .

# 进入镜像并自动映射挂载本地目录
$ docker run -v $PWD:$PWD -w $PWD --rm -it ledge:builder bash

这样一键启动了开发或部署运行环境,省时省力。

CMake编译LLVM程序

LLVM良好的模块化设计导致的一个结果,就是配置参数比较复杂,提供专门获取编译参数的工具llvm-config:

$ llvm-config --cxxflags --ldflags --libs --system-libs

如果直接手写到Makefile, 大致会类似:

$ clang++ -g -O3 `llvm-config --cxxflags --ldflags --system-libs --libs core orcjit native` program.cpp -o program

如果是使用CMake管理的项目,则可以这样:

# 要求必须找到LLVM
find_package(LLVM REQUIRED CONFIG)

message(STATUS "Found LLVM: ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVM: ${LLVM_DIR}")
message(STATUS "Using LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}")
message(STATUS "Using LLVM_DEFINITIONS: ${LLVM_DEFINITIONS}")

include_directories(${LLVM_INCLUDE_DIRS})
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LLVM_DEFINITIONS}")

# 执行shell命令获取链接参数
execute_process(COMMAND llvm-config --ldflags
  WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  TIMEOUT 20
  RESULT_VARIABLE LLVM_LDFLAGS_result
  OUTPUT_VARIABLE LLVM_LDFLAGS
  ERROR_VARIABLE LLVM_LDFLAGS_error
  OUTPUT_STRIP_TRAILING_WHITESPACE
  ERROR_STRIP_TRAILING_WHITESPACE
)

message(STATUS "Using LLVM_LDFLAGS: ${LLVM_LDFLAGS}")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LLVM_LDFLAGS}")

# 执行shell命令获取链接库清单
execute_process(COMMAND llvm-config --libs core orcjit native --system-libs
  COMMAND tr "\n" " "
  WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  TIMEOUT 20
  RESULT_VARIABLE LLVM_LIBS_result
  OUTPUT_VARIABLE LLVM_LIBS
  ERROR_VARIABLE LLVM_LIBS_error
  OUTPUT_STRIP_TRAILING_WHITESPACE
  ERROR_STRIP_TRAILING_WHITESPACE
)

message(STATUS "Using LLVM_LIBS: ${LLVM_LIBS}")

add_executable(program src/program.cpp)
target_link_libraries(program ${LLVM_LIBS})

这样就可以愉快的引入LLVM并编译现有程序了。

总结

工欲善其事必先利其器。