笔者在使用 `rbenv`[1] 安装 ruby
时,遇到一个头文件缺失导致无法编译失败的问题。
本文会记录笔者对该问题产生的原因分析,并通过分析 clang
源码的方式提供一个通用的解决方案。
通过该方案,可以解决所有类似的头文件缺失问题。
rbenv
安装 ruby 失败
笔者是执行 rbenv install 2.7.2
命令时遇到了问题。
控制台输出如下:
image-20201214210046129
根据添加 --verbose
参数,我们可以得到更加详细的错误信息:
clang -I. -Iinclude -fPIC -arch x86_64 -O3 -Wall -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DOPENSSLDIR=""/Users/kukudeaidian/.rbenv/versions/2.7.2/openssl/ssl"" -DENGINESDIR=""/Users/kukudeaidian/.rbenv/versions/2.7.2/openssl/lib/engines-1.1"" -D_REENTRANT -DZLIB -DZLIB_SHARED -DNDEBUG -I/Users/kukudeaidian/.rbenv/versions/2.7.2/include -MMD -MF apps/app_rand.d.tmp -MT apps/app_rand.o -c -o apps/app_rand.o apps/app_rand.c
In file included from apps/app_rand.c:10:
In file included from apps/apps.h:13:
In file included from ./e_os.h:16:
In file included from include/openssl/e_os2.h:243:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/include/inttypes.h:21:15: fatal error: 'inttypes.h' file not found
#include_next <inttypes.h>
^~~~~~~~~~~~
1 error generated.
make[1]: *** [apps/app_rand.o] Error 1
make: *** [all] Error 2
从上面的日志中,我们可以发现三个关键点:
rbenv
最终调用了clang
执行编译任务clang
执行编译任务时,无法找到系统库头文件<inttypes.h>
clang
命令缺失-isysroot
参数
系统库文件查找路径
通常情况下,我们可以添加参数 -isysroot <dir>
的方式解决上面的报错:
clang -c -o apps/app_rand.o apps/app_rand.c -I. -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
但是,本次是通过 rbenv
命令执行 ruby
的安装任务,所以上面的解决方案行不通。
Clang driver
为了查找变通方案,我们需要先了解一下 `Clang driver`[2] 。
在 llvm 编译器高级用法:第三方库插桩中,我们曾经提到过 clang
会按照以下顺序执行。
image-20201215013642049
而负责组装每个任务的就是 clang Driver
。
clang Driver
的处理逻辑分为以下几步:
- Parse: Option Parsing:解析传入的参数
- Pipeline: Compilation Action Construction:根据每个输入的文件和类型,组建
action
(比如PreprocessJobAction
)- 通过
-ccc-print-phases
可以查看action
- 通过
clang -ccc-print-phases -c t0.c t1.c
- 0: input, "t0.c", c
- 1: preprocessor, {0}, cpp-output
- 2: compiler, {1}, ir
- 3: backend, {2}, assembler
- 4: assembler, {3}, object
5: bind-arch, "x86_64", {4}, object
- 6: input, "t1.c", c
- 7: preprocessor, {6}, cpp-output
- 8: compiler, {7}, ir
- 9: backend, {8}, assembler
- 10: assembler, {9}, object
11: bind-arch, "x86_64", {10}, object
Bind: Tool & Filename Selection:根据 action
选择对应的工具和文件名信息
- 通过
-ccc-print-bindings
可以查看对应的工具和文件名信息clang -ccc-print-bindings -c t0.c t1.c -arch arm64 -arch armv7
# "aarch64-apple-darwin19.6.0" - "clang", inputs: ["t0.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-9a2aac.o"
# "arm-apple-darwin19.6.0" - "clang", inputs: ["t0.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-7a4059.o"
# "arm-apple-darwin19.6.0" - "darwin::Lipo", inputs: ["/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-9a2aac.o", "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-7a4059.o"], output: "t0.o"
# "aarch64-apple-darwin19.6.0" - "clang", inputs: ["t1.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-950abb.o"
# "arm-apple-darwin19.6.0" - "clang", inputs: ["t1.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-044386.o"
# "arm-apple-darwin19.6.0" - "darwin::Lipo", inputs: ["/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-950abb.o", "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-044386.o"], output: "t1.o"
Translate: Tool Specific Argument Translation:根据输入的参数转为不同tool
的参数
原始参数:
代码语言:javascript复制 xcrun --sdk iphoneos clang -target arm64-apple-ios8.0 main.m -v
各个 tool
的参数:
clang -cc1 -triple arm64-apple-ios8.0.0 -o main-a28fc8.o -x objective-c main.m
ld -arch arm64 -platform_version ios 8.0.0 -o a.out main-a28fc8.o
- 比如,插桩参数
-fsanitize-coverage=trace-pc-guard
会变为-fsanitize-coverage-type=3 -fsanitize-coverage-trace-pc-guard
参数
Execute:真正的执行不同的工具
为了方便理解,我们可以将下面的图片和上面的流程对应:
DriverArchitecture
第一版方案:通过环境变量控制头文件搜索路径
因为 mac
与 Darwin tool chain
对应,所以我们需要重点关注 Darwin tool chain
相关的逻辑。
通过查看 clang::driver::toolchains::Darwin
相关的代码,我们会发现下面的执行逻辑:
Compilation
调用 clang::driver::toolchains::Darwin
的 TranslateArgs
函数时,会在命令行的 -isysroot
参数缺失的情况下,通过环境变量 SDKROOT
获取。
image-20201214222425629
image-20201215013626760
如下图,通过 export
命令设置环境变量后,clang
命令可以正常执行
export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
image-20201215010258221
xcrun
考虑到不同的系统会对应不同的 SYSROOT
,每次都通过 export
方式设置环境变量比较繁琐,我们需要换一种更简单的配置方式。
根据 xcrun
的帮助文件,我们可以发现,通过 xcrun
命令可以方便的调整不同系统对应的 SDKROOT
路径。
SDKROOT
Specifies the default SDK to be used when looking up tools (some tools may have SDK specific versions).
This environment variable is also set by xcrun to be the absolute path to the user provided SDK (either via SDKROOT or the
--sdk option), when it is used to invoke a normal developer tool (build tools like xcodebuild or make are exempt from this
behavior).
For example, if xcrun is used to invoke clang via:
xcrun --sdk macosx clang test.c
then xcrun will provide the full path to the macosx SDK in the environment variable SDKROOT. That in turn will be used by
clang(1) to automatically select that SDK when compiling the test.c file.
比如,我们可以通过以下代码完成编译:
代码语言:javascript复制// 编译 iPhone 项目
xcrun -l --sdk iphoneos clang -target arm64-apple-ios8.0 main.m
// 编译 macosx 项目
xcrun -l clang main.m
优化方案:xcrun
根据上面的信息,我们可以尝试使用 xcrun
调用安装命令:
image-20201215012636981
通过截图,我们可以发现 xcrun rbenv install 2.7.2
命令组合可以安装 ruby
。
总结
通过本文,我们可以得到以下经验:当因为标准库头文件缺失导致编译失败时,可以通过搭配 xcrun
完成编译任务。
参考资料
[1]
rbenv
: https://github.com/rbenv/rbenv
[2]
Clang driver
: https://clang.llvm.org/docs/DriverInternals.html