At PSPDFKit, we use Ccache — a wrapper that sits between Xcode and Clang — to accelerate our build times. We’ve been using it successfully for more than five years now. In this post, we explain how Ccache can be used in modern projects that mix Objective-C and Swift.
Ccache and Modules
As Swift moved to a stable ABI this year, we started mixing Objective-C and Swift in our SDK. In order for that to work, we had to enable Modules, which was a setting Ccache didn’t support.
This changed with the release of ccache 4.0, and
-fmodules is now officially supported! This will speed up compilation times for a warm cache by 3–5 minutes, which is significant.
Before you get too excited: Ccache helps for C, C++, and Objective-C code. It doesn’t know about Swift (yet).
File Cloning on APFS
The latest release of Ccache learned quite a few new tricks, so our recommended configuration (
~/.ccache/ccache.conf) is now the following:
# Modules requires both direct and depend mode. run_second_cpp = true depend_mode = true direct_mode = true # Faster file copying (cloning) on APFS. file_clone = true inode_cache = true # Accept more file changes before recompiling. sloppiness = modules, include_file_mtime, include_file_ctime, time_macros, pch_defines, file_stat_matches, clang_index_store, system_headers max_size = 100.0G
All settings are thoroughly explained in the Ccache manual. The important part is to add
sloppiness and enable both direct and depend mode to support
The most exciting change is that 4.0 learned to use file cloning (AKA “reflinks”) on Btrfs, XFS, and APFS to copy data to and from the cache efficiently.
Earlier versions could use hard links; however, those were problematic as they could be overridden. If you’re curious, reflinks are implemented via a new header and C function
clonefile on macOS.
Multiple Source Files
After we enabled everything, we encountered a new problem: Caching failed because Ccache detected multiple input files.
This line is relevant (enable logging to get this output, via
log_file = /tmp/ccache.log in
[2020-10-22T13:28:08.138633 43404] Multiple input files: /Users/steipete/Builds/PSPDFKit-apwojecgwxmuoieabhwzqzmecixq/Build/Intermediates.noindex/PSPDFKit.build/Debug-iphonesimulator/PSPDFKit.framework.build/all-product-headers.yaml and /Users/steipete/Projects/PSPDFKit/iOS/PSPDFKit/Glyphs/PSPDFTextBlock.m
For some reason
all-product-headers.yaml was detected as an input file! It’s referenced via the
-ivfsoverlay parameter, which can “overlay the virtual filesystem described by file over the real file system.”
I’ve been reading the file and it doesn’t seem as it would redefine any paths. Instead — at least for our project — it simply lists the used headers.
Unfortunately, there’s no way to teach Ccache to ignore a specific setting; this has to be changed in the source code. To fix this, I forked Ccache and opened a pull request where this flag is simply ignored.
Custom Homebrew Formula
The build instructions for Ccache are easy enough, so for a first test, I simply replaced the binary that was installed via Homebrew. I verified that the files are now correctly cached, and I moved on to create a custom formula so that we can use the fork in our CI infrastructure.
Adding a custom “tap” to Homebrew is easy — it’s simply a GitHub repository. One file is one formula. We already have one at PSPDFKit so I simply added the customized formula there.
First, tag your custom version. I used
v4.0.pspdfkitas the tag.
Next, find the existing formula via https://formulae.brew.sh/ and copy it to the new tap.
Modify the link to point to your custom archive. GitHub offers zipped source download links when you click on a tag, so it’s easy to replace the existing URL.
Homebrew also uses SHA-256 to verify downloads, so calculate the new hash from the terminal via
openssl dgst -sha256.
If there are any bottles, remove them, as they’re prebuilt binary variants that won’t contain our modifications.
That’s it! Now you can install the new formula:
brew tap pspdfkit-labs/tap brew reinstall pspdfkit-labs/tap/ccache
The new version of Ccache is significantly faster thanks to file cloning on APFS, and with our customization, it can now also be used again for Objective-C code, which will greatly reduce our compilation times.