회사에서 테스트 배포를 할 때마다 수동으로 signed apk 파일을 만들어내고 있는 중이라
Github를 사용하고 있으니 Action을 써볼까 하는 생각이 들었다.
검색해보니 나는 잘 모르는 스크립트 파일을 작성을 해야하는데 어쩌구 저쩌구....
순식간에 승훈이가 됨
이 글에서는 기본적인 개념이나 방법 말고 실제로 제가 workflow를 만들면서 겪은 문제만을 서술합니다.
app.gradle
에서 추출하는 apk 파일의 이름을 만들어주는데, 이름 사이에 빌드된 시간이 들어간다.
그래서 파일 이름이 매번 바뀌니까 파일 이름을 하드코딩으로 박아넣을 수 없었다.
- name: Get signed debug apk file path
id: debugApk
run: echo "apkfile=$(find app/build/outputs/apk/debug/*.apk)" >> $GITHUB_OUTPUT
이렇게 추출된 apk 파일이 있는 경로에서 파일 이름을 *
로 와일드카드?를 써서 찾을 수 있었다.
그리고 경로를 GITHUB_OUTPUT
이라는 변수?에 저장하는 듯 싶다.
(아직 뭘 몰라서 눈치껏 파악하고 있습니다)
저렇게 저장하면
file_path: '${{ steps.debugApk.outputs.apkfile }}'
이런식으로 꺼내쓸 수 있는 듯 하다.
나는 keystore를 적용할 때 properties 파일을 사용했다.
1. 로컬에 keystore.properties
파일을 만들어서 로컬에서 빌드할 때 사용함
// ./keystore.properties 파일
storeFile=./keystore.jks
storePassword={비밀번호}
keyAlias={이름}
keyPassword={비밀번호}
// app.gradle.kts 파일
fun getProperties(path: String) = Properties().apply {
load(FileInputStream(rootProject.file(path)))
}
android {
// ...
signingConfigs {
getByName("debug") {
// keystore.properties 파일을 읽어와서 사용
val keystoreProp = getProperties("keystore.properties")
storeFile = File(keystoreProp.getProperty("storeFile"))
storePassword = keystoreProp.getProperty("storePassword")
keyAlias = keystoreProp.getProperty("keyAlias")
keyPassword = keystoreProp.getProperty("keyPassword")
}
}
buildTypes {
getByName("debug") {
signingConfig = signingConfigs.getByName("debug")
}
// ...
}
// ...
}
2. keystore.properties
, keystore.jks
파일은 gitignore에 등록해서 리모트에 올라가지 않음
3. Action에서 keystore.jks
파일을 생성한다.
- name: Decode Keystore
env:
ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
run: |
echo $ENCODED_STRING > keystore-b64.txt
base64 -d keystore-b64.txt > keystore.jks
cp keystore.jks ./app/keystore.jks # <- 복사하는 이유는 후술..
로컬에 만들어진 .jks
파일을 base64로 인코딩하고, 그걸 secret에 변수로 저장해놓는다.
그러면 Action에서는 인코딩된 문자열을 다시 디코딩해서 keystore.jks
파일로 만든다.
4. Action에서 build하기 전에 keystore.properties
파일을 생성함
- name: Create keystore.properties file
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
echo "storeFile=./keystore.jks" > keystore.properties
echo "storePassword=$KEYSTORE_PASSWORD" >> keystore.properties
echo "keyAlias=$KEYSTORE_ALIAS" >> keystore.properties
echo "keyPassword=$KEY_PASSWORD" >> keystore.properties
리눅스 명령어를 잘 몰라서 그냥 echo
네번 써서 파일에 네줄로 썼습니다..
빌드하기 전에 keystore.properties
파일을 만들어서 빌드할 때 사용될 수 있도록 했다.
만들어지는 위치는 프로젝트의 최상위 경로(./)에 만들어진다.
5. 빌드한다.
- name: Build signed debug apk
run: ./gradlew assembleDebug --stacktrace
그런데 빌드하는 과정에서 자꾸 에러가 나서 실패를 하는 것이다...
Execution failed for task ':app:validateSigningDebug'.
> Keystore file '/home/runner/.gradle/daemon/8.2/./keystore.jks' not found for signing config 'debug'.
검색을 해봐도 다들 경로를 확인하라는 답변이 제일 많이 보였다.
그래서 keystore.jks
의 경로를 계속 바꿔가면서 해봤는데도 해결이 안됐다.
그러다가 ./
경로와 ./app/
경로 둘 다에 keystore.jks
를 복사해서 넣으니 정상적으로 빌드가 됐다!
- name: Decode Keystore
env:
ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
run: |
echo $ENCODED_STRING > keystore-b64.txt
base64 -d keystore-b64.txt > keystore.jks
cp keystore.jks ./app/keystore.jks # <- 왜그런진 모르겠지만 복사해준다
apk에 sign이 정상적으로 된건지 확인을 해보고 싶었다.
다들 keytool을 사용해서 확인하라는데 자꾸 제대로 확인이 안됐다.
C:\...\...\...>keytool -printcert -jarfile app-debug.apk
Not a signed jar file
찾아보니 Android SDK 버전이 올라가면서 버전이 올라갔고?? apksigner
라는걸 사용해야하는 듯 싶었다.
(잘 몰라서 정말 미안합니다)
apksigner
는 Android SDK 경로에 있다.
Tool > SDK Manager > Android SDK Location
cmd에서 apksigner
가 있는 경로로 이동하여 다음과 같이 치면 서명을 확인할 수 있다.
apksigner verify --print-certs {apk 파일 경로}
name: Android CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Decode Keystore
env:
ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
run: |
echo $ENCODED_STRING > keystore-b64.txt
base64 -d keystore-b64.txt > keystore.jks
cp keystore.jks ./app/keystore.jks
- name: Create keystore.properties file
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
echo "storeFile=./keystore.jks" > keystore.properties
echo "storePassword=$KEYSTORE_PASSWORD" >> keystore.properties
echo "keyAlias=$KEYSTORE_ALIAS" >> keystore.properties
echo "keyPassword=$KEY_PASSWORD" >> keystore.properties
- name: Build signed debug apk
run: ./gradlew assembleDebug --stacktrace
- name: Get signed debug apk file path
id: debugApk
run: echo "apkfile=$(find app/build/outputs/apk/debug/*.apk)" >> $GITHUB_OUTPUT
- name: slack upload file
uses: MeilCli/slack-upload-file@v4.0.0
with:
slack_token: ${{ secrets.SLACK_TOKEN }}
channel_id: ${{ secrets.SLACK_CHANNEL_APK_DELIVERY }}
content: 'content'
file_path: '${{ steps.debugApk.outputs.apkfile }}'
if_no_files_found: error
initial_comment: 'create signed apk file: ${{ job.status }}'