[Android] Guthub Actions로 CI 구축하기

Taekyu Lim·2022년 8월 2일
0

먼저 Github의 Repository 관리 정책은 Git Flow를 따르는 것을 전제로 합니다.

이번 포스트에서는 feature -> develop으로 merge할 때 CI를 적용할 것입니다.
다음 포스트에서 develop -> release로 merge할 때 CD를 적용할 것입니다.

1. 사전 준비

(1). KeyStore 만들기

안드로이드 스튜디오에서 KeyStore를 하나 만들고 다음 정보들을 기억합니다.

Keystore Password
Key Alias
Key Alias Password

(2). 시스템 path에 설정

시스템 path에 위 3개 항목과 KeyStore의 위치를 지정합니다.

$ vim .zshrc

(3). build.gradle 작성

App 수준 build.gradle에 다음과 같이 설정합니다.

android {
    signingConfigs {
        debug {
            storeFile file("debug.keystore")
        }
        release {
            storeFile file("$System.env.KEYSTORE_PATH")
            storePassword "$System.env.KEYSTORE_PASSWORD"
            keyAlias "$System.env.KEY_ALIAS"
            keyPassword "$System.env.KEY_PASSWORD"
        }
    }
}

KeyStore 파일이 원격 Repository에 올라가지 않도록 파일을 프로젝트 외부에 두고
그 위치를 KEYSTORE_PATH로 지정하는 것이 좋습니다.
Github Action이 동작할 때는
해당 KeyStore를 특정 위치에 디코딩하고 그 위치를 KEYSTORE_PATH를 통해 다시 알려줄 것입니다.

(4). secrets 추가

(2)에서 지정한 path를 Github Action에서 사용할 수 있도록 Secrets에 추가합니다.

https://github.com/[Account]/[Repository]/settings/secrets/actions/new

(5). Keystore 추가

release.keystore를 인코딩 해서 Secrets에 추가합니다.

openssl base64 -in [release.keystore 경로 및 파일명] -out keystore.txt
cat keystore.txt

cat의 결과로 출력된 문자열을 복사하여 Secrets에 추가합니다.

2. CI 파일 생성

(1). Android CI 파일 생성

다음과 같이 Repositoy > Actions > Newflow를 선택합니다.

Android를 검색하고 Android CI의 configure를 선택합니다.

다음 경로에 yaml 파일을 하나 생성되는데, 코드는 다음과 같습니다.

name: Android CI

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      run: ./gradlew build

(2). Actions 동작 조건 및 job 기술

on 아래에는 어떤 동작을 트리거로 Github Actions를 동작시킬 지 기술합니다.

push, pull_request는 각각 push, pull request 되었을 때 Actions가 동작하도록 지정합니다.
branches에 기술된 branch에 push, pull_request 등 동작이 발생하면 Actions가 동작합니다.

jobs는 여러 job을 가질 수 있습니다.
각각의 job은 동일한 runner에서 실행되는 step의 집합입니다.
각 step에 기술된 내용은 순서대로 진행되며, 같은 runner에서 실행되기 때문에 step간에는 서로 정보를 공유합니다.

기본 코드를 지우고, 다음과 같이 develop 브랜치로 pr이 발생할 때 동작하는 build 라는 job을 만듭니다.

name: Android CI

on:
  pull_request:
    branches: [ "develop" ]

jobs:
  build:
  
    runs-on: ubuntu-latest

job 아래에 runs-on을 통해서 job이 동작할 운영체제를 지정할 수 있습니다.
다음 중 하나를 선택할 수 있습니다.
위 코드에서는 최신 우분투 버전을 선택했습니다.

(3). 환경설정 로드

다음은 환경설정 내용을 불러옵니다.
앞에서 로컬에서 설정했던 Keystore 관련 정보들을 Secrets에 저장했었는데,
이 정보들을 Actions가 사용할 수 있도록 불러오는 단계입니다.

jobs:
  build:

    runs-on: ubuntu-latest
    env:
      KEYSTORE_PATH: ${{ secrets.KEYSTORE_PATH }}
      KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
      KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
      KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

위 코드처럼 env 아래에 각 환경변수들을 추가합니다.
이제 로컬 시스템에 환경변수가 추가된 것과 동일하게 Actions에서 위 환경변수들을 사용할 수 있습니다.

(4). 빌드 환경 설정 구축 step

다음은 job 아래에 steps를 기술합니다.
steps는 여러 step들로 구성됩니다.
다음 코드는 차례로 다음 동작들을 수행하는 코드입니다.

  • 코드 체크아웃
  • 자바 버전 설정(11)
  • gradle 권한 부여
  • 단위 테스트
  • 코드 포맷 체크(ktlint)
  • 정적 분석(detekt)

단위 테스트, 코드 포맷, 정적 분석 등은 필요한 작업들만 골라서 추가해도 무방합니다.

    steps:
    - uses: actions/checkout@v3
    - name: set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Unit test
      run: ./gradlew test
    - name: Code format check
      run: ./gradlew ktlintCheck
    - name: Static analysis
      run: ./gradlew detekt

(5). gradle 빌드 step

다음은 실제 apk를 만드는 빌드 step 입니다.
먼저 KEYSTORE_FILE_BASE64에 인코딩해서 올렸던 파일을 디코딩해서 release.keystore 파일을 만듭니다.
가 다음 clean build를 실행합니다.
여기서는 integration만 할 것이므로 debug빌드를 했는데, 필요에 따라 release 빌드를 해도 무방합니다.

    - name: Create Key Store
      run: echo "${{ secrets.KEYSTORE_FILE_BASE64 }}" | base64 -d > release.keystore
    - name: Build with Gradle
      run: ./gradlew clean assembleDebug

(6). apk 정보 획득 step

다음은 apk 정보를 얻기 위한 step 입니다.

apk-path는 id를 지정하여 다른 step에서 apk-path의 output 값을 활용할 수 있도록 했고,
apk-info에서 apk-path에 접근하여 apk의 정보를 추출합니다.

    - name: Get apk path
      id: apk-path
      run: |
        path=$(find **/build/outputs/apk -name '*.apk' -type f | head -1)
        echo "::set-output name=path::$path"
        name=$(find **/build/outputs/apk -name '*.apk' -type f -exec basename {} \; | head -1)
        echo "::set-output name=name::$name"

(7). 슬랙에 apk 업로드

다음은 생성한 apk를 슬랙에 업로드하는 step 입니다.
이번 step을 위해서 슬랙 token과 채널 이름이 필요합니다.

먼저 슬랙 연동을 위해 다음 슬랙 api 페이지에 접속하여 로그인하면 다음과 같이 새로운 앱을 만들 수 있습니다.

https://api.slack.com/apps

앱 생성이 완료되면 OAuth 권한도 부여합니다.

권한 부여 후에는 위로 올라가서 Install to Workspace를 클릭합니다.

이제 다음과 같이 OAuth 토큰이 발급되었습니다. copy를 눌러 복사하고 Secret에 추가할 것입니다.

다음과 같이 Github의 Secret에 토큰을 추가합니다.

슬랙에 공유할 채널 이름도 하나 추가합니다.
모두 추가하면 Secrets는 다음과 같이 구성됩니다.

이제 슬랙 업로드를 위한 코드를 다음과 같이 작성합니다.

    - uses: MeilCli/slack-upload-file@v1
      with:
        slack_token: ${{ secrets.SLACK_OAUTH_TOKEN }}
        channels: ${{ secrets.SLACK_CHANNEL_NAME }}
        file_path: ${{ steps.apk-path.outputs.path }}
        file_name: ${{ steps.apk-path.outputs.name }}
        file_type: 'apk'
        initial_comment: '${{ steps.apk-path.outputs.name }} Deploy debug version to Slack Successfully'

방금 입력한 슬랙의 토큰과 채널 이름을 설정하고,
위에서 얻은 apk-path에서 경로와 이름을 가져와서 설정합니다.
이제 develope 브랜치로 pr을 하나 만들어보겠습니다.

Actions 항목에서 빌드가 수행될 것입니다.

잘 수행되면 다음과 같이 초록색 체크 아이콘이 뜹니다.

슬랙 업로드도 잘 된것을 볼 수 있습니다.

3. 전체 코드

name: Android CI

on:
  pull_request:
    branches: [ "develop" ]

jobs:
  build:

    runs-on: ubuntu-latest
    env:
      KEYSTORE_PATH: ${{ secrets.KEYSTORE_PATH }}
      KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
      KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
      KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}


    steps:
    - uses: actions/checkout@v3
    - name: set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Unit test
      run: ./gradlew test
    - name: Code format check
      run: ./gradlew ktlintCheck
    - name: Static analysis
      run: ./gradlew detekt
    - name: Create Key Store
      run: echo "${{ secrets.KEYSTORE_FILE_BASE64 }}" | base64 -d > release.keystore
    - name: Build with Gradle
      run: ./gradlew clean assembleDebug

    - name: Get apk path
      id: apk-path
      run: |
        path=$(find **/build/outputs/apk -name '*.apk' -type f | head -1)
        echo "::set-output name=path::$path"
        name=$(find **/build/outputs/apk -name '*.apk' -type f -exec basename {} \; | head -1)
        echo "::set-output name=name::$name"

    - uses: MeilCli/slack-upload-file@v1
      with:
        slack_token: ${{ secrets.SLACK_OAUTH_TOKEN }}
        channels: ${{ secrets.SLACK_CHANNEL_NAME }}
        file_path: ${{ steps.apk-path.outputs.path }}
        file_name: ${{ steps.apk-path.outputs.name }}
        file_type: 'apk'
        initial_comment: '${{ steps.apk-path.outputs.name }} Deploy debug version to Slack Successfully'

이번 포스트에서는 CI를 위해 코드를 검증하고 빌드하여 공유를 하는 방법을 알아보았습니다.
다음 포스트에서는 CD를 위해 Play Console에 공유하는 방법까지 알아보겠습니다.

profile
Mobile Engineer (Android, Flutter) / 정보관리 기술사

0개의 댓글