在上一章中,我们看到LLVM的构建系统是一个庞然大物:它包含数百个构建文件和数千个相互交错的构建依赖关系。更不用说,它还包含需要为异构源文件提供自定义构建指令的目标。这些复杂性促使LLVM采用了一些先进的构建系统特性,更重要的是,一个更结构化的构建系统设计。在本章中,我们的目标将是了解一些重要的指令,以便在进行树内和树外LLVM开发时编写更简洁和表达性更强的构建文件。
在本章中,我们将涵盖以下主要主题:
- 探索LLVM重要CMake指令的词汇表
- 通过CMake在树外项目中集成LLVM
技术要求
与第1章构建LLVM时节约资源类似,您可能希望从源代码构建LLVM的副本。可选的是,由于本章将涉及大量的CMake构建文件,您可能希望为CMakeLists.txt准备一个语法高亮插件(例如VSCode的CMake Tools插件)。所有主要的IDE和编辑器应该都有现成的。此外,熟悉基本的CMakeLists.txt语法是可取的。
探索LLVM重要CMake指令的词汇表
由于选择底层构建系统的灵活性更高,LLVM已经从GNU autoconf切换到CMake。自那以后,LLVM提出了许多自定义的CMake函数、宏和规则来优化自身的使用。本节将向您概述其中最重要和最常用的一些。我们将学习如何以及何时使用它们。
使用CMake函数添加新库
库是LLVM框架的构建块。然而,在为新库编写CMakeLists.txt时,您不应该使用普通CMakeLists.txt文件中出现的普通add_library指令,如下所示:
# 在树内CMakeLists.txt文件中...
add_library(MyLLVMPass SHARED
MyPass.cpp) # 添加新LLVM库时不要这样做
在这里使用普通的add_library有几个缺点,如下所示:
- 如第1章构建LLVM时节约资源*中所示,LLVM更倾向于使用全局CMake参数(即
BUILD_SHARED_LIBS)来控制是否所有组件库应该静态或动态构建。使用内置指令很难做到这一点。 - 与前一点类似,LLVM更倾向于使用全局CMake参数来控制一些编译标志,例如是否在代码库中启用运行时类型信息(RTTI)和C++异常处理。
- 通过使用自定义的CMake函数/宏,LLVM可以创建自己的组件系统,为开发人员提供更高层次的抽象,以便以更简单的方式指定构建目标依赖关系。
因此,您应该始终使用下面显示的add_llvm_component_library CMake函数:
# 在CMakeLists.txt中
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
这里,LLVMFancyOpt是最终的库名称,而FancyOpt.cpp是源文件。
在常规CMake脚本中,您可以使用target_link_libraries来指定给定目标的库依赖关系,然后使用add_dependencies来分配不同构建目标之间的依赖关系,以创建明确的构建顺序。当您使用LLVM的自定义CMake函数创建库目标时,有一种更简单的方式来完成这些任务。
通过在add_llvm_component_library中使用LINK_COMPONENTS参数(或add_llvm_library,它是前者的底层实现),您可以指定目标的链接组件:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp
LINK_COMPONENTS
Analysis ScalarOpts)
或者,您可以使用在函数调用之前定义的LLVM_LINK_COMPONENTS变量来做同样的事情:
set(LLVM_LINK_COMPONENTS
Analysis ScalarOpts)
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
组件库只不过是带有特殊意义的普通库,当涉及到您可以使用的LLVM构建块时。如果您选择构建它,它们也包含在庞大的libLLVM库中。组件名称与实际库名称略有不同。如果您需要从组件名称映射到库名称,您可以使用以下CMake函数:
llvm_map_components_to_libnames(output_lib_names
<组件名称列表>)
如果您想直接链接到普通库(非LLVM组件库),可以使用LINK_LIBS参数:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp
LINK_LIBS
${BOOST_LIBRARY})
要为库目标分配一般构建目标依赖关系(相当于add_dependencies),可以使用DEPENDS参数:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp
DEPENDS
intrinsics_gen)
intrinsics_gen是一个常见目标,代表生成包含LLVM内建定义的头文件的过程。
每个文件夹添加一个构建目标
许多LLVM自定义CMake函数有一个涉及源文件检测的缺陷。假设您有一个像这样的目录结构:
/FancyOpt
|___ FancyOpt.cpp
|___ AggressiveFancyOpt.cpp
|___ CMakeLists.txt
这里,您有两个源文件FancyOpt.cpp和AggressiveFancyOpt.cpp。顾名思义,FancyOpt.cpp是这个优化的基础版本,而AggressiveFancyOpt.cpp是同一功能的另一种更激进的版本。自然,您会希望将它们分成单独的库,以便用户可以选择是否希望在他们的正常工作负载中包含更激进的版本。因此,您可能会这样写一个CMakeLists.txt文件:
# 在/FancyOpt/CMakeLists.txt中
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
add_llvm_component_library(LLVMAggressiveFancyOpt
AggressiveFancyOpt.cpp)
不幸的是,这将生成错误消息,告诉您类似于找到未知源AggressiveFancyOpt.cpp…之类的信息,当处理第一个add_llvm_component_library语句时。
LLVM的构建系统强制执行更严格的规则,以确保所有同一文件夹中的C/C++源文件都被添加到同一个库、可执行文件或插件中。要解决这个问题,需要将两个文件中的一个分到另一个文件夹中,如下所示:
/FancyOpt
|___ FancyOpt.cpp
|___ CMakeLists.txt
|___ /AggressiveFancyOpt
|___ AggressiveFancyOpt.cpp
|___ CMakeLists.txt
在/FancyOpt/CMakeLists.txt中,我们有以下内容:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
add_subdirectory(AggressiveFancyOpt)
最后,在/FancyOpt/AggressiveFancyOpt/CMakeLists.txt中,我们有以下内容:
add_llvm_component_library(LLVMAggressiveFancyOpt
AggressiveFancyOpt.cpp)
这些是使用LLVM的自定义CMake指令添加构建目标(组件)库的基本知识。在接下来的两节中,我们将向您展示如何使用不同的LLVM特定CMake指令添加可执行文件和Pass插件构建目标。
使用CMake函数添加可执行文件和工具
类似于add_llvm_component_library,要添加一个新的可执行目标,我们可以使用add_llvm_executable或add_llvm_tool:
add_llvm_tool(myLittleTool
MyLittleTool.cpp)
这两个函数具有相同的语法。然而,只有通过add_llvm_tool创建的目标才会包含在安装中。还有一个全局CMake变量LLVM_BUILD_TOOLS,它启用/禁用这些LLVM工具目标。
这两个函数也可以使用DEPENDS参数分配依赖关系,类似于我们之前介绍的add_llvm_library。但是,您只能使用LLVM_LINK_COMPONENTS变量来指定要链接的组件。
使用CMake函数添加Pass插件
虽然我们将在本书后面介绍Pass插件的开发,但添加一个Pass插件的构建目标比以前(当LLVM还在使用带有一些特殊参数的add_llvm_library时)更加简单。我们可以简单地使用以下命令:
add_llvm_pass_plugin(MyPass
HelloWorldPass.cpp)
LINK_COMPONENTS、LINK_LIBS和DEPENDS参数在这里也可用,其用法和功能与add_llvm_component_library中的相同。
这些是一些最常见和最重要的LLVM特定CMake指令。使用这些指令不仅可以使您的CMake代码更简洁,还可以帮助您与LLVM自己的构建系统同步,以防您想进行一些树内开发。在下一节中,我们将向您展示如何将LLVM集成到树外的CMake项目中,并利用我们在本章中学到的知识。
树内与树外开发
在本书中,树内开发意味着直接向LLVM项目贡献代码,例如修复LLVM错误或向现有LLVM库添加新功能。另一方面,树外开发要么表示为LLVM创建扩展(例如编写LLVM Pass),要么表示在其他项目中使用LLVM库(例如使用LLVM的代码生成库实现您自己的编程语言)。
了解树外项目的CMake集成
在树内项目中实现功能对于原型设计来说很好,因为大部分基础设施已经存在。然而,在许多情况下,将整个LLVM源代码树拉入您的代码库并不是最好的主意,与创建树外项目并将其链接到LLVM库相比。例如,您只想使用LLVM的功能创建一个小型代码重构工具,并在GitHub上开源,因此告诉GitHub上的开发人员下载几GB大小的LLVM源代码树以及您的小工具可能不是一种愉快的体验。
至少有两种方法可以配置树外项目以链接到LLVM:
- 使用
llvm-config工具 - 使用LLVM的CMake模块
这两种方法都可以帮助您解决所有细节,包括头文件和库路径。然而,后者创建的CMake脚本更简洁、易读,这对于已经使用CMake的项目来说更可取。本节将展示使用LLVM的CMake模块将其集成到树外CMake项目中的基本步骤。
首先,我们需要准备一个树外的(C/C++)CMake项目。我们在上一节中讨论的核心CMake函数/宏将帮助我们完成这一任务。让我们看看我们的步骤:
-
我们假设您已经有了以下
CMakeLists.txt骨架,用于需要链接到LLVM库的项目:project(MagicCLITool) set(SOURCE_FILES main.cpp) add_executable(magic-cli ${SOURCE_FILES})无论您是尝试创建一个生成可执行文件的项目,就像我们在前面的代码块中看到的那样,还是其他工件(如库或甚至LLVM Pass插件),现在最大的问题是如何获取
include path以及library path。 -
为了解决
include path和library path,LLVM提供了标准的CMake包接口,您可以使用find_packageCMake指令来导入各种配置,如下所示:project(MagicCLITool) find_package(LLVM REQUIRED CONFIG) include_directories(${LLVM_INCLUDE_DIRS}) link_directories(${LLVM_LIBRARY_DIRS}) …为了使
find_package技巧起作用,您需要在为此项目调用CMake命令时提供LLVM_DIRCMake变量:$ cmake -DLLVM_DIR=<LLVM安装路径>/lib/cmake/llvm …确保它指向
LLVM安装路径下的lib/cmake/llvm子目录。 -
在解决了包含路径和库之后,是时候将主可执行文件链接到LLVM的库了。LLVM的自定义CMake函数(例如
add_llvm_executable)在这里将非常有用。但首先,CMake需要能够找到这些函数。下面的代码片段导入了LLVM的CMake模块(更具体地说,是
AddLLVMCMake模块),其中包含了我们在前一节中介绍的那些LLVM特定的函数/宏: -
下面的代码片段使用我们在前一节中学到的CMake函数添加可执行构建目标:
find_package(LLVM REQUIRED CONFIG) … list(APPEND CMAKE_MODULE_PATH ${LLVM_CMAKE_DIR}) include(AddLLVM) -
添加库目标没有什么不同:
find_package(LLVM REQUIRED CONFIG) … include(AddLLVM) add_llvm_library(MyMagicLibrary lib.cpp LINK_COMPONENTS Support Analysis) -
最后,添加LLVM Pass插件:
find_package(LLVM REQUIRED CONFIG) … include(AddLLVM) add_llvm_pass_plugin(MyMagicPass ThePass.cpp) -
在实践中,您还需要注意LLVM特定的定义和RTTI设置:
find_package(LLVM REQUIRED CONFIG) … add_definitions(${LLVM_DEFINITIONS}) if(NOT ${LLVM_ENABLE_RTTI}) # 对于非MSVC编译器 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") endif() add_llvm_xxx(source.cpp)这对于RTTI部分尤其如此,因为默认情况下,LLVM不是用RTTI支持构建的,但普通的C++应用程序是。如果您的代码和LLVM的库之间存在RTTI不匹配,将抛出编译错误。
尽管在LLVM的源代码树内开发很方便,但有时将整个LLVM源代码封装在您的项目中可能不可行。因此,我们必须创建一个树外项目,并将LLVM作为库集成。本节向您展示了如何将LLVM集成到基于CMake的树外项目中,并充分利用我们在探索LLVM重要CMake指令的词汇表部分中学到的LLVM特定CMake指令。
总结
本章深入探讨了LLVM的CMake构建系统。我们看到了如何使用LLVM自己的CMake指令编写简洁有效的构建脚本,无论是对于树内还是树外开发。学习这些CMake技能可以使您的LLVM开发更高效,并为您提供更多选项,以便将LLVM功能与其他现有代码库或自定义逻辑结合使用。
在下一章中,我们将介绍LLVM项目中另一个重要的基础设施,称为LLVM LIT,这是一个易于使用但通用的框架,用于运行各种测试。
429



被折叠的 条评论
为什么被折叠?



