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=falseCatatan: 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: true4. 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.jsonatauyarn.lock(menentukan versi library TurboModule pihak ketiga). - File build sistem Android:
android/build.gradledanandroid/app/build.gradle. - Versi NDK dan CMake: Pastikan versi NDK yang didefinisikan pada
ndkVersiondibuild.gradleterikat 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-cache7. 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
ninjaataumakeuntuk C++ akan selesai dalam fraksi detik jika artifact.cxxdipulihkan 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.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!