Build Android React Native pada New Architecture (TurboModules dan Fabric) membutuhkan waktu signifikan di CI. Penyebab utamanya adalah kompilasi berulang kode C++ (Hermes, ReactCommon, Folly, dan pustaka pihak ketiga) pada ephemeral runner GitHub Actions. Artikel ini membedah arsitektur caching untuk memangkas durasi build dari hitungan puluhan menit menjadi hitungan menit.

1. Identifikasi Bottleneck: C++ TurboModule dan Node Modules

Pada arsitektur baru React Native, interaksi antara JavaScript dan native runtime tidak lagi melewati async bridge berbasis JSON, melainkan antarmuka langsung C++ via JSI (JavaScript Interface). Dampaknya, setiap dependensi di node_modules yang memanfaatkan TurboModule (seperti react-native-reanimated atau react-native-screens) harus dikompilasi dari source code C++ menggunakan Android NDK dan CMake.

Tanpa mekanisme caching pada runner CI yang bersifat ephemeral (dihancurkan setelah job selesai), proses kompilasi kode native C++ dan translasi task Java/Kotlin akan dieksekusi dari nol pada setiap commit (cold build). Akibatnya, waktu build CI membengkak hingga 15–25 menit per eksekusi.

2. Optimasi Konfigurasi android/gradle.properties

Sebelum mengonfigurasi pipeline CI, pastikan daemon Gradle dikonfigurasi untuk mengeksekusi task secara paralel dan menyimpan cache output. Ubah file android/gradle.properties:

# Alokasi memori JVM yang optimal untuk runner standar GitHub Actions (7 GB RAM)
org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024m

# Aktifkan Gradle Build Cache lokal
org.gradle.caching=true

# Jalankan task independen secara paralel
org.gradle.parallel=true

# Pertahankan daemon antar task dalam satu sesi build
org.gradle.daemon=false

# Konfigurasi spesifik Android
android.useAndroidX=true
android.enableJetifier=false
Catatan: Di lingkungan CI, tetapkan org.gradle.daemon=false untuk menghindari konsumsi resource memori berlebih yang dapat memicu Out of Memory (OOM) killer pada runner.

3. Implementasi gradle/actions/setup-gradle

Hindari caching manual direktori ~/.gradle menggunakan actions/cache standar. Pendekatan manual tersebut rentan menghasilkan cache korup akibat lock files dan state transient. Gunakan action resmi gradle/actions/setup-gradle.

Action ini secara cerdas menyimpan dependensi (JAR/AAR wrapper) dan entri Gradle Build Cache, serta mengabaikan file ephemeral seperti *.lock sebelum proses upload cache berlangsung.

- name: Set up JDK 17
  uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'

- name: Setup Gradle
  uses: gradle/actions/setup-gradle@v3
  with:
    cache-read-only: false
    gradle-home-cache-cleanup: true

4. Strategi Caching Direktori .cxx dan Artifact CMake

Secara default, Gradle Build Cache tidak menyimpan seluruh intermediate artifact kompilasi C++ yang dihasilkan oleh CMake/NDK. Direktori .cxx yang berada di modul aplikasi (misal android/app/.cxx) dan di dalam modul native node_modules/*/android/.cxx sering kali dibangun ulang.

Gunakan actions/cache terpisah khusus untuk artifact native C++ ini guna mencegah pemanggilan compiler clang++ yang memakan banyak siklus CPU.

- name: Cache NDK .cxx Build Artifacts
  uses: actions/cache@v4
  with:
    path: |
      android/app/.cxx
      android/app/build/intermediates/cxx
    key: ndk-${{ runner.os }}-${{ hashFiles('package-lock.json', 'android/build.gradle', 'android/app/build.gradle') }}
    restore-keys: |
      ndk-${{ runner.os }}-

5. Penentuan Cache Key Invalidation yang Presisi

Invalidasi cache yang salah menyebabkan kompilasi ulang yang tidak perlu (false positive) atau kegagalan build runtime karena linking header yang usang (false negative). Cache key wajib merepresentasikan seluruh dependensi hulu:

  • Lockfile dependensi JavaScript: package-lock.json atau yarn.lock (menentukan versi library TurboModule pihak ketiga).
  • File build sistem Android: android/build.gradle dan android/app/build.gradle.
  • Versi NDK dan CMake: Pastikan versi NDK yang didefinisikan pada ndkVersion di build.gradle terikat secara konsisten pada hashing key.

6. Workflow Lengkap GitHub Actions

Berikut implementasi file .github/workflows/android-ci.yml yang terintegrasi:

name: Android Build CI

on:
  pull_request:
    paths:
      - 'android/**'
      - 'src/**'
      - 'package-lock.json'
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3
        with:
          cache-read-only: ${{ github.ref != 'refs/heads/main' }}

      - name: Cache NDK CMake Output
        uses: actions/cache@v4
        with:
          path: |
            android/app/.cxx
            android/app/build/intermediates/cxx
          key: ${{ runner.os }}-ndk-${{ hashFiles('package-lock.json', 'android/app/build.gradle') }}
          restore-keys: |
            ${{ runner.os }}-ndk-

      - name: Build Android Release APK
        working-directory: android
        run: ./gradlew assembleRelease --no-daemon --build-cache

7. Verifikasi Metrik: Cold Build vs Warm Build

Untuk memvalidasi efektivitas caching, periksa log Gradle console setelah pipeline selesai:

  • Task bertanda FROM-CACHE: Task output diambil langsung dari build cache lokal Gradle.
  • Task bertanda UP-TO-DATE: Input dan output task tidak berubah sehingga kompilasi dilewati.
  • Task yang mengeksekusi ninja atau make untuk C++ akan selesai dalam fraksi detik jika artifact .cxx dipulihkan secara benar.

Perbandingan Metrik Tipikal:

  • Cold Build (Tanpa Cache): ~18 – 22 menit. Seluruh dependensi node, wrapper Gradle, NDK CMake, dan task Kotlin dieksekusi secara berurutan.
  • Warm Build (Cache Hit Penuh): ~3 – 5 menit. Tahap kompilasi C++ TurboModule dilewati, pipeline hanya menjalankan packaging asset JS (Hermes bundling) dan DEX merge.

Pola caching berlapis ini meminimalisasi overhead runner CI dan mempercepat feedback loop deployment aplikasi React Native.