Github Action 사용해서 Slack으로 apk 파일 전송

th.k·2024년 2월 26일
0
post-thumbnail

회사에서 테스트 배포를 할 때마다 수동으로 signed apk 파일을 만들어내고 있는 중이라
Github를 사용하고 있으니 Action을 써볼까 하는 생각이 들었다.

검색해보니 나는 잘 모르는 스크립트 파일을 작성을 해야하는데 어쩌구 저쩌구....

순식간에 승훈이가 됨

이 글에서는 기본적인 개념이나 방법 말고 실제로 제가 workflow를 만들면서 겪은 문제만을 서술합니다.

목표

  1. 타겟 브랜치에 pull request나 push가 되면 자동으로 signed apk(debug)를 만들어내기
  2. 만들어진 signed apk가 자동으로 Slack 채널로 전달됨

내가 겪은 문제

Slack으로 파일 전송

1. 달라지는 apk 파일 이름 처리

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 }}'

이런식으로 꺼내쓸 수 있는 듯 하다.

apk에 sign하기

keystore 적용하기

나는 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

not found for signing config 'debug'

그런데 빌드하는 과정에서 자꾸 에러가 나서 실패를 하는 것이다...

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  # <- 왜그런진 모르겠지만 복사해준다

sign 확인하기

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 파일 경로}

전체 workflow

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 }}'

참조

profile
고생끝에롹이온다

0개의 댓글