Reduce React Native XCode build time

This was a tough cookie to crack for me. I've been annoyed with how the iOS builds of the KopiRun app take like 45minutes to build in AppCenter and was trying to figure out a way to cache the DerivedData that gets compiled when you build the app.

For most cases, it's not really an issue since you can get incremental builds if you're building it locally and compile times are pretty spiffy from my experience at least. However that is very different from trying to archive the project and in a Release configuration. XCode will clean your project and remove any cached data so everytime you try to archive your app, it'll compile everything from scratch. Compiling locally on the M1 Mac isn't the issue but I had to find a way to reduce the build time for iOS.

Journey

I spent a lot of time searching and trying out different methods of optimisation, from setting the project's Optimization Level, Compilation Mode, ccache, xcode-archive-cache, cocoapods-binary-cache but none of it worked well enough or some were just totally irrelevant and only for pure XCode Projects which is not the case. There was a post by someone else about using FastLane to do it but unfortunately we don't use FastLane for KopiRun at the moment.

Searching even more landed me on the project buildcache-action, which is a GitHub action for react-native projects, created by one of the core team members of the react-native-firebase project. It uses buildcache and he saw a 40-50% reduction in the amount of time it took to build the app and I am seeing such improvements too.

On the first run, it will take the same amount of time as your normal builds but subsequent runs will definitely be 40-50% faster.

Solve

You'll have to download the latest release from here and extract that to a directory that's in your path.

The instructions below are for MacOS:

  1. Download v0.27.1 of buildcache
  2. Extract buildcache
  3. Move buildcache to /usr/local/bin
  4. Make buildcache executable
  5. Verify the buildcache command works by invoking the version argument.
terminal
Copy
curl -L -O https://github.com/mbitsnbites/buildcache/releases/download/v0.27.1/buildcache-macos.zip
unzip buildcache-macos.zip
mv buildcache/bin/buildcache /usr/local/bin/buildcache
chmod +x /usr/local/bin/buildcache
buildcache -V

BuildCache version 0.27.1
Copyright (c) 2018-2021 Marcus Geelnard

Supported back ends:
  local - Local file system based cache (level 1)
  Redis - Remote in-memory cache (level 2)
  HTTP  - Remote webdav cache (level 2)
  S3    - Remote object storage based cache (level 2)

Third party components:
  cJSON 1.7.13
  cpp-base64 2.rc.04
  hiredis 1.0.1
  HTTPRequest
  lua 5.3.4
  lz4 1.9.2
  xxhash 0.8.0
  zstd 1.4.5

So now that you've installed buildcache, it's really easy to setup. We'll need to make our clang and clang++ commands appear before the original commands as we need to wrap these commands with the buildcache command.

We can do so by checking the path variable that's configured on your system.

terminal
Copy
echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

clang and clang++ are located in /usr/bin but we need our commands to come before so that it takes priority when we invoke the commands which allows us to not have to modify the project extensively and it's basically a drop in replacement.

You can either add a path e.g.~/bin to your $PATH variable and do the symlink there or just symlink buildcache into /usr/local/bin like I am going to do:

  1. Create symlinks for both clang and clang++
  2. Use the which command to determine the location of the command we're invoking
terminal
Copy
ln -s /usr/local/bin/buildcache /usr/local/bin/clang
ln -s /usr/local/bin/buildcache /usr/local/bin/clang++
which clang
#/usr/local/bin/clang
which clang++
#/usr/local/bin/clang++

Once you have the above set-up, you just need to make a small modification to your Podfile. This change does not affect other users who do not have buildcache installed in their systems and they can compile the app as per normal albeit a bit slower on subsequent runs.

Copy
post_install do |installer|
react_native_post_install(installer)

installer.pods_project.targets.each do |target|
  target.build_configurations.each do |config|
    config.build_settings["CC"] = "clang"
    config.build_settings["LD"] = "clang"
    config.build_settings["CXX"] = "clang++"
    config.build_settings["LDPLUSPLUS"] = "clang++"
  end
end
end

That's it! You can verify the the time it takes to build by appending the time command infront of the xcodebuild command. Run it twice and compare the difference!

terminal
Copy
time xcodebuild -workspace ios/kopirun.xcworkspace -scheme kopirun -sdk iphoneos -configuration Release archive -archivePath $PWD/ios/build/kopirun.xcarchive

Comments

Matt

23 December, 2021 at 12:12 PM

Cheers for this - very interesting. I'm a little confused though - the instructions are all about setting up buildcache on a mac, so I could install it locally and have faster builds locally. But how does this help on AppCenter, is there a way to get buildcache on there too so that AppCenter build agents can use it?

Does it involve something like taking the bash script you posted, and running that as part of the build (I see you can add custom build scripts in AppCenter for example)


Zane

23 December, 2021 at 12:12 PM

Hey Matt,

Yes you're absolutely right. That's the way that you can do it. You can take the commands and put them in the AppCenter scripts to use buildcache on their build machines too. I am no longer using buildcache as xcode-archive-cache works a bit faster. Though I could probably check my git history for the version that I implemented in the AppCenter scripts when I was using buildcache.

I think I should write up a post about an end to end implementation on putting together all the parts to improve build times in AppCenter.


Alaa

08 February, 2022 at 10:02 AM

Hi !

Where are the steps where the cache is created/restored/used ?


Zane

08 February, 2022 at 11:02 AM

Hi Alaa,

buildcache handles all of that automatically and the above is all that you need to get the cache working.


Hailson Junior

24 June, 2022 at 2:06 AM

Where do buildcache store cache?

I'm trying to use it on my GitLab pipeline.


Zane

25 June, 2022 at 12:06 PM

Per the usage docs, the default directory where the cache is stored is `$HOME/.buildcache`.

You can set the `BUILDCACHE_DIR` environment variable to any other directory that you would like the cache to be stored in.

See https://github.com/mbitsnbites/buildcache/blob/master/doc/usage.md#using-with-visual-studio--msbuild


Leave a comment