EAS 없이 Expo 로컬 빌드: 서명 자료를 모으고도 남는 다섯 가지
클라우드 빌드를 떠나기로 하고(왜 옮기는지는 따로 적었습니다) 서명 자료부터 모았습니다. iOS 배포 인증서와 프로비저닝 프로파일, 안드로이드 업로드 키스토어와 비밀번호 두 개, 스토어 API 키까지 한자리에 놓고 확인했습니다. 코드 서명 신원도 유효하게 잡혔습니다.
그래서 이제 빌드하면 되는 줄 알았습니다. 빌드 전에 앱 하나를 재봤더니 아니었습니다.
아예 없는 네이티브 프로젝트
리포에 android/ 도 ios/ 도 없습니다. Expo 의 CNG(Continuous Native Generation)를 쓰면 네이티브 프로젝트는 소스가 아니라 산출물입니다. expo prebuild 가 설정을 읽어 그때그때 만듭니다.
.gitignore 에도 그렇게 적혀 있습니다.
/ios/
/android/여기에 함정이 하나 있습니다. 서명 설정은 android/app/build.gradle 과 Xcode 프로젝트에 들어갑니다. 둘 다 생성되는 파일입니다. 손으로 고쳐 놓고 다음에 prebuild 를 한 번 돌리면 그대로 사라집니다.
그래서 서명 설정은 파일이 아니라 config plugin 으로 넣어야 합니다. 플러그인은 생성 과정에 끼어들어 매번 같은 수정을 다시 적용합니다. 이 앱에는 아직 플러그인이 없습니다. 그것부터 만들어야 합니다.
이 컴퓨터에 없는 버전 번호
eas.json 에 이렇게 적혀 있습니다.
"cli": { "appVersionSource": "remote" }versionCode 와 buildNumber 를 리포가 아니라 서버가 들고 있다는 뜻입니다. 실제로 app.json 에는 두 값이 아예 없습니다. 지금까지는 클라우드 빌드가 올릴 때마다 하나씩 올려 줬습니다.
물어보면 알려줍니다.
$ npx eas-cli build:version:get -p android
Android versionCode - 2
$ npx eas-cli build:version:get -p ios
iOS buildNumber - 3로컬에서 빌드한다면 다음 번호는 3과 4입니다. 모르고 2로 올리면 스토어가 이미 쓴 번호라며 거부합니다. 빌드가 20분 걸린 다음에 알게 되는 종류의 거부입니다.
빌드 서비스가 심어 주던 OTA 채널
이 앱은 expo-updates 를 씁니다. 업데이트를 어느 채널에서 받을지는 eas.json 에 있습니다.
"production": { "channel": "production" }이 값은 네이티브 프로젝트 안에 심겨야 앱이 읽습니다. 심는 일을 지금까지 클라우드 빌드가 해 왔습니다. 로컬 빌드는 그 단계를 거치지 않으므로, 아무것도 하지 않으면 채널이 비어 있는 앱이 나갑니다. 설치는 되고 실행도 되는데 업데이트만 영영 못 받습니다.
runtimeVersion 이 fingerprint 정책인 것도 같이 봐야 합니다. 네이티브 구성이 달라지면 지문이 달라지고, 지문이 다르면 기존에 올려둔 업데이트가 이 빌드에 적용되지 않습니다.
안드로이드에만 먼저 필요한 도구
| 이 컴퓨터 | |
|---|---|
| Xcode | 26.6 |
| JDK | 없음 |
| Android SDK | 없음 |
iOS 는 도구가 이미 있어서 서명 자료만으로 다음 단계로 갈 수 있습니다. 안드로이드는 JDK 17 과 Android SDK 를 먼저 깔아야 시작합니다.
이게 발목을 잡은 자리가 하나 더 있습니다. 키스토어의 비밀번호 두 개가 제 역할인지 확인하려면 keytool 이 필요한데, 그게 JDK 와 함께 옵니다. 저장소 비밀번호와 키 비밀번호는 다른 값이고 바꿔 넣으면 서명이 깨집니다. 그러니 확인해야 하는데, 확인 도구가 아직 없습니다.
디버그 키로 서명되는 릴리스
처음 이 글을 쓸 때는 이 항목만 확인하지 못한 채로 두었습니다. 확인하려면 prebuild 를 돌려 파일을 만들어야 했기 때문입니다. 돌려 보고 다시 적습니다.
생성된 android/app/build.gradle 은 이렇습니다.
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
release {
// Caution! In production, you need to generate your own keystore file.
signingConfig signingConfigs.debug
}
}릴리스가 디버그 서명 설정을 그대로 가리킵니다. 그 설정이 쓰는 키스토어의 비밀번호는 android 이고 별칭은 androiddebugkey 입니다. 리액트 네이티브를 써 본 사람이면 다 아는 값이고, 알라고 만든 값입니다. 개발 중에 기기에 설치하는 데만 쓰라는 뜻입니다.
주석까지 친절하게 달려 있습니다. 그런데 주석은 빌드를 막지 않습니다. 그대로 릴리스를 빌드하면 서명은 되고 AAB 도 나옵니다. 거부는 그 파일을 Play 에 올릴 때 옵니다.
iOS 쪽도 같이 봤습니다. 생성된 Xcode 프로젝트에는 DEVELOPMENT_TEAM 만 들어 있고 CODE_SIGN_STYLE 은 아예 없었습니다. 없으면 자동 서명입니다. 손에 든 프로파일로 서명하려면 Manual 로 바꾸고 프로파일 이름과 서명 신원을 함께 지정해야 합니다.
시작에 불과했던 서명
정리하면 이렇습니다. 서명 자료를 모으는 일은 클라우드 빌드를 떠나는 데 필요하지만 충분하지 않습니다. 빌드 서비스는 서명만 해 주던 게 아니라 번호를 매기고, 채널을 심고, 네이티브 프로젝트를 만들고, 도구를 준비하는 일을 함께 하고 있었습니다.
떠날 때 눈에 보이는 것은 서명 자료뿐입니다. 나머지는 없어진 뒤에야 무엇이 있었는지 알게 됩니다. 그래서 빌드를 시도하기 전에 한 번 재보는 편이 낫습니다. 다섯 가지 중 네 가지는 빌드를 돌리기 전에 명령 한 줄로 확인할 수 있었습니다.
다음은 prebuild 를 돌려 네이티브 프로젝트를 만들고, 거기서 서명 설정이 실제로 어떻게 되어 있는지 보는 일입니다.
함께 읽기
- Expo config plugin이 반영되지 않는 이유: prebuild와 mod 순서생명시계 앱에 Face ID 잠금을 붙였습니다. expo-local-authentication 을 설치하고 app.json 에 플러그인과 권한 문구를 적었습니다.
- Android Studio 없이 실기기 빌드: 최소 환경 838MB와 각 구성요소의 역할Expo SDK 57 앱을 안드로이드 실기기에 올리려고 맥을 열었는데, 빌드에 필요한 것이 하나도 없었습니다. java -version은 "Unable to locate a Java Runtime"을 냈고 ANDROIDHOME은 비어 있었으며 adb는 명령어 자체가 없었습니다. Xcode만 들어 있는 기계였습니다.
- App Store Connect 업로드: Xcode 로그인 없이 API 키로아카이브는 만들어졌습니다. 이제 IPA 로 뽑아 올리기만 하면 되는데, 여기서 Xcode 에 애플 계정을 붙이라는 요구가 세 번 나옵니다. 세 번 다 피할 수 있습니다.
- Expo 로컬 릴리스 빌드: 17분 만에 죽고 3분 48초에 끝난 이유클라우드 빌드를 떠나 로컬에서 릴리스를 만들어 보기로 했습니다. 서명 자료는 다 모아 두었고 도구도 깔았습니다. 첫 빌드는 17분 51초를 돌고 나서 죽었습니다.
- Expo CNG: android 와 ios 폴더가 리포에 없는 이유Expo 로 만든 앱의 리포를 클론하면 ios/ 도 android/ 도 없습니다. .gitignore 를 열어 보면 아예 막혀 있습니다.