RSS듀오랩스
모바일 앱

Expo 로컬 릴리스 빌드: 17분 만에 죽고 3분 48초에 끝난 이유

작성자
듀오랩스 대표·6분 읽기

클라우드 빌드를 떠나 로컬에서 릴리스를 만들어 보기로 했습니다. 서명 자료는 다 모아 두었고 도구도 깔았습니다. 첫 빌드는 17분 51초를 돌고 나서 죽었습니다.

Execution failed for task ':expo-updates:kspReleaseKotlin'.
> A failure occurred while executing com.google.devtools.ksp.gradle.KspAAWorkerAction
   > Metaspace

649개 태스크가 실행된 뒤였습니다. 거의 끝난 자리에서 메모리가 모자랐습니다.

릴리스를 못 견디는 기본 메모리 설정

android/gradle.properties 를 열어 보니 이렇게 적혀 있었습니다.

org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m

Expo 템플릿의 기본값입니다. 개발 빌드는 이 안에서 끝나는데, 릴리스에서 Kotlin Symbol Processing 이 도는 순간 Metaspace 512MB 를 넘깁니다. 힙이 아니라 메타스페이스라는 점이 헷갈리는 대목입니다. -Xmx 만 올려서는 같은 자리에서 다시 죽습니다.

16GB 기계에서 이렇게 올렸습니다.

org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=2048m

고치면 안 되는 그 파일

여기서 한 번 멈춰야 합니다. android/npx expo prebuild 가 만드는 산출물이고 .gitignore 로 막혀 있습니다. gradle.properties 도 그 안에 있습니다. 손으로 고쳐 놓으면 다음에 prebuild 를 돌리는 순간 기본값으로 돌아갑니다.

그러면 다음에 릴리스를 빌드하는 사람이 17분을 다시 씁니다. 그 사람이 몇 달 뒤의 자신일 가능성이 높습니다.

그래서 config plugin 에 넣었습니다. 플러그인은 생성 과정에 끼어들어 매번 같은 수정을 다시 적용합니다.

withGradleProperties(config, (cfg) => {
  const KEY = 'org.gradle.jvmargs';
  const VALUE = '-Xmx4096m -XX:MaxMetaspaceSize=2048m';
  const found = cfg.modResults.find((it) => it.type === 'property' && it.key === KEY);
  if (found) found.value = VALUE;
  else cfg.modResults.push({ type: 'property', key: KEY, value: VALUE });
  return cfg;
});

서명까지 들고 있는 같은 플러그인

이 플러그인은 원래 다른 일을 하려고 만든 것이었습니다. Expo 가 만드는 build.gradle 은 릴리스 빌드가 디버그 키를 가리킵니다.

release {
    // Caution! In production, you need to generate your own keystore file.
    signingConfig signingConfigs.debug
}

그 디버그 키스토어는 비밀번호가 android 이고 별칭이 androiddebugkey 인, 알라고 만든 값입니다. 그대로 빌드하면 서명은 되고 AAB 도 나옵니다. 거부는 Play 에 올릴 때 옵니다.

그래서 플러그인이 signingConfigs.release 를 만들고 buildTypes.release 가 그것을 가리키게 바꿉니다. 키스토어 경로와 비밀번호는 파일에 적지 않고 빌드할 때 환경변수로만 넣습니다.

release {
    storeFile file(System.getenv("DUOLABS_KEYSTORE") ?: "...")
    storePassword System.getenv("DUOLABS_STORE_PASSWORD")
    keyAlias System.getenv("DUOLABS_KEY_ALIAS")
    keyPassword System.getenv("DUOLABS_KEY_PASSWORD")
}

메모리 설정도 서명 설정도 같은 이유로 같은 자리에 있습니다. 생성물을 고치지 않고 생성 과정을 고친다는 것 하나입니다.

3분 48초로 끝난 두 번째 빌드

prebuild 를 다시 돌려 두 설정이 모두 살아 있는 것을 확인하고 빌드했습니다.

BUILD SUCCESSFUL in 3m 48s
769 actionable tasks: 737 executed, 32 up-to-date

17분과 4분의 차이는 대부분 캐시입니다. 죽은 빌드도 649개 태스크의 결과는 남겨 두었습니다. 실패한 빌드가 아무것도 남기지 않는다고 생각하면 재시도가 더 무섭게 느껴집니다.

이름이 아니라 지문으로 하는 서명 확인

빌드가 성공했다는 것과 올바른 키로 서명됐다는 것은 다릅니다. 디버그 키로 서명해도 빌드는 성공합니다. 그래서 결과물의 인증서 지문을 키스토어의 것과 맞춰 봤습니다.

$ keytool -printcert -jarfile app-release.aab
SHA1: E5:73:41:91:8D:...

$ keytool -list -v -keystore upload.jks
SHA1: E5:73:41:91:8D:...

같았습니다. 소유자 필드가 CN=, OU=, O= 처럼 비어 있는데, 자동 생성된 업로드 키스토어에서는 흔한 모양입니다. 이름으로 확인했다면 여기서 헷갈렸을 것입니다.

결과

같은 날 iOS 도 같은 길로 갔습니다. 아카이브를 만들고 IPA 로 뽑아 App Store Connect 에 올렸습니다. 빌드 서비스도, Xcode 계정 로그인도 쓰지 않았습니다. 안드로이드는 서명된 AAB 까지 만들고 업로드는 다음으로 미뤘습니다.

떠나 보니 클라우드 빌드가 해 주던 일은 서명만이 아니었습니다. 메모리 설정처럼 눈에 띄지도 않는 것들이 함께 딸려 있었습니다. 그 목록은 먼저 적어 둔 글에 있습니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.