DEV Community

Jhin Lee
Jhin Lee

Posted on • Updated on

Optimize Flutter iOS Build using ccache

The Problem

I've been working on a flutter using firebase dependencies. (cloud store, cloud storage, cloud_function so on...)

The build time has always been painful with ios build. On my local machine (MacBook pro 16" with 32GB ram, intel), It takes 10min for a clean build. 2-5min for a rebuild. (Slow, but maybe not too wrong. I can live with that, but..)

When I run the build from the GitHub action for CI/CD, It takes almost an hour without a cache. Not only the time consuming but also the $$$$ (macOS runner costs $0.08 per min * 60 = 4.8$ per build)

Two years back, to address this issue, I found this and applied: https://github.com/invertase/firestore-ios-sdk-frameworks
This helped to save 40min in the CI/CD, but It's always been painful to update firebase related dependencies. Since I have fixed the iOS SDK version, it easily introduces regression or builds fail when it doesn't match with flutter's firebase dependency.

For the moment, the pre-compiled SDK doesn't work with the latest flutter firebase libraries: https://github.com/firebase/flutterfire/issues/9761
So it takes an hour to build my project in GitHub action. I can't live with it!

Solution: the ccache!

After some research, I found from the React Native doc about the caching for iOS build: https://reactnative.dev/docs/build-speed#use-a-compiler-cache
I instantly thought that I could apply this to my flutter project too.

It was done in simple steps.

For the local machine (Same as the react native doc)

  1. Install the ccache brew install ccache
  2. Add symlinks

    ln -s ccache /usr/local/bin/gcc
    ln -s ccache /usr/local/bin/g++
    ln -s ccache /usr/local/bin/cc
    ln -s ccache /usr/local/bin/c++
    ln -s ccache /usr/local/bin/clang
    ln -s ccache /usr/local/bin/clang++
    
  3. Make sure it's used

    $ which gcc
    /usr/local/bin/gcc
    

    If it doesn't show the /usr/local/bin/gcc, make sure adjust your $PATH in .profile or .bashrc or .zshrc.

  4. Update the Podfile

    post_install do |installer|
        react_native_post_install(installer)
    
        # ...possibly other post_install items here
    
        installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            # Using the un-qualified names means you can swap in different implementations, for example ccache
            config.build_settings["CC"] = "clang"
            config.build_settings["LD"] = "clang"
            config.build_settings["CXX"] = "clang++"
            config.build_settings["LDPLUSPLUS"] = "clang++"
        end
        end
    
        __apply_Xcode_12_5_M1_post_install_workaround(installer)
    end
    
  5. Export the ccache config before building

    export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
    export CCACHE_FILECLONE=true
    export CCACHE_DEPEND=true
    export CCACHE_INODECACHE=true
    
  6. Test your build: flutter build ios

    The first build would still be slow since the caches are not generated yet.

  7. Check if the cache is created.

    $ ccache -s
    Summary:
    Hits:             196 /  3068 (6.39 %)
        Direct:           0 /  3068 (0.00 %)
        Preprocessed:   196 /  3068 (6.39 %)
    Misses:          2872
        Direct:        3068
        Preprocessed:  2872
    Uncacheable:        1
    Primary storage:
    Hits:             196 /  6136 (3.19 %)
    Misses:          5940
    Cache size (GB): 0.60 / 20.00 (3.00 %)
    
  8. Rerun the build: flutter build ios

The build time should be much faster now on your local machine.

For the GitHub Action

Same steps as the local machine. We need to use ccache-action to install the ccache easily and let it automatically store and restore the cache: https://github.com/hendrikmuhs/ccache-action

  1. Add this to the workflow file

      - name: Install Ccache
        uses: hendrikmuhs/ccache-action@v1.2
        with:
          max-size: 5G
    

    The max cache size is 500M by default, but it was too small for my project. With the default setting, it didn't hit the cache, so I had to increase it to 5G.

  2. Update the build command

      - name: Build flutter app
        run: |
          export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
          export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
          export CCACHE_FILECLONE=true
          export CCACHE_DEPEND=true
          export CCACHE_INODECACHE=true
          ccache -s
          flutter build ipa --export-options-plist=ios/exportOptions.plist --release
    

Like the local machine, the first build will still be slow but from the second build with the cache built, you'll see it's much faster.

You can inspect the action if the cache hits.
Post ccache

With this, I was able to reach almost the same build time (around 20min) without using the pre-compiled firestore SDK.

Top comments (0)