原文地址:
Using ccache for Fun and Profit
作者
Peter Steinberger
我们的
PSPDFKit
项目超过 60 万行代码,并且代码量还在增长。尽管我们致力于写简洁而高效的代码,但是这个项目很大,而且有许多边界情况需要尤其注意。在
PSPDFKit 5 for iOS
项目上,编译时间尤其成为一个令人头痛的问题:每次编译都很慢。
我们的安卓 SDK 也有同样的问题,几个月前我们的安卓负责人在技术栈中引入了 ccache 来处理冗长的 C++ NDK 编译时间,我也是从那个时候开始接触 ccache。
ccache 是个啥?
ccache
是一个编译缓存器,它会在实际编译之前先检查缓存。它有直接和预处理模式,而且由于在
Clang 3.2
版本之前是不支持
ccache
插件,所以在
Clang 3.2
之前会有一些问题,但是现在
Clang
的版本是 3.2.3,所以没有
Clang
不支持的问题。
ccache
是一个具有悠久历史的项目,其主要焦点是快速正确。
网上搜到“ccache xcode”的信息都是过时无效的信息,经过我快速的尝试网上的方法,都无法配置好使其正常工作。随着我们的代码库越来越复杂,同时我们的 Jenkins 工作集群数也有 10 台 Mac,现在测试时间从几乎无法忍受变成了正真无法忍受。在 Twitter 抱怨现在每天的工作就是管理 Jenkins 工作集群之后,Facebook 的 Christian Legnitto(他之前在 Apple 负责 OS X 版本管理工作)建议我们尝试
ccache
。
Let’s get started
使用以下命令安装
ccache
:
brew install ccache
如果你没安装
Homebrew
,请移步
这里
,先去安装
Homebrew
,如果你不想移步,就直接使用以下命令安装
Homebrew
:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
为了让
Xcode
调用
ccache
,我们需要一个小脚本来配置一些环境变量,然后再调用
ccache
。将这个脚本保存到您项目的某个地方,并将其命名为
ccache-clang
。
#!/bin/sh
if type -p ccache >/dev/null 2>&1; then
export CCACHE_MAXSIZE=10G
export CCACHE_CPP2=true
export CCACHE_HARDLINK=true
export
CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches
exec ccache /usr/bin/clang "$@"
else
exec clang "$@"
fi
根据你的具体情况,如果你的项目中有
C++
的文件,你可能还需要一个命名为
ccache-clang++
的脚本,并在这个脚本里这么写:
#!/bin/sh
if type -p ccache >/dev/null 2>&1; then
export CCACHE_MAXSIZE=10G
export CCACHE_CPP2=true
export CCACHE_HARDLINK=true
export
CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches
exec ccache /usr/bin/clang "$@"
else
exec clang++ "$@"
fi
这样看起来是不是有点复杂,如果没有命中缓存,那么将会按照之前的编译方式一样编译,而不是报
ccache not found
(找不到缓存)的错误(
ccache
内置
shell
脚本,所以检查缓存很迅速)。
创建 shell 脚本方法:
创建 touch ccache-clang
打开脚本 open -a xcode ccache-clang
粘贴脚本内容
执行脚本 chmod 755 ccache-clang
如果去学习
ccache
的配置,你会发现有很多选项可选。上面我们使用的是一种相当激进的缓存策略,同时运行良好。对于你自己的项目,你可能在没有
CCACHE_SLOPPINESS
的情况下开始,然后在一切运行良好的情况下一次性添加缓存。
这里最重要的参数是
CCACHE_CPP2
,这个参数用于解决
Clang
将处理预处理器的文件输出,并可能会发现许多你没有注意到的潜在问题,例如由于宏扩展导致的不必要的括号。使用此选项会稍微减慢编译时间,但是要比完全没有使用
ccache
要快得多。Peter Eisentraut 写了一篇关于这个问题的
好文章
。
您还需要在
Xcode
中定义
CC
变量。在
PSPDFKit
中,我们在
.xcconfig
文件中执行此操作,这个文件在我们所有项目中共享(这是一个很好的统一的项目配置,和易于阅读和查找)。同时,您可以直接在
Xcode
项目设置内配置:
CC = "$(SRCROOT)/../Resources/ccache-clang"
就这么多了!下次编译的时候会比正常慢一点,你可以在终端中使用
ccache -s
来查看
ccache
是否正常工作。刚开始时应该有很多缓存没有命中,但是当缓存开始渐渐替代之后的编译时,编译速度将会变得快起来。
坑来了
路不平的地方就有坑:
ccache
有一些缺点。
不支持
Clang
的
modules
,如果检测到
-fmodules
,
ccache
就会失效。因此,为了兼容
ccache
,你需要用老旧的
# import
替换你项目中所有优雅的
@import UIKit
,以及所有使用
ccache
带来的问题,比方说宏的问题。在
PSPDFKit
项目中我们采用了
Objective-C++
的形式,当我们使用很多
C++
代码时,就无法使用
modules
了,所以这一点(
ccache
不支持
modules
)并没有影响到我们。
modules
会自动链接用到的
framework
,但是在禁用了
modules
以后,你需要手动添加用到的
framework
,这个工作很无趣,但是也很快就做完。
还需要停止使用
.pch
。苹果不推荐使用
.pch
,而且一般认为使用
.pch
是不好的编程风格,哪里用到就在哪里导入会比
.pch
要好。对我们而言,删除那些
.pch
还是很容易的。当然,
ccache
没法帮你缓存
Swift
文件。虽然
Swift
也使用
Clang
,但是
ccache
对
Swift
文件束手无策。也许
ccache
最终会支持
Swift
,但我指望不上。因为
Swift
至今没有稳定,甚至我们要在
Swift
的两个版本之间做二进制兼容,我们没法用
Swift
来编写我们的 SDK,所以
ccache
不支持
Swift
的问题,对我们不是问题。