Github Action์„ ์ด์šฉํ•œ Android CI/CD ๊ตฌ์ถ• with Firebase

CI/CD๋ž€? ๐Ÿค”

GitHub Actions๋Š” ๊ฐœ๋ฐœ์ž๋“ค์ด ์ฝ”๋“œ ๋ณ€๊ฒฝ์„ ์ž๋™์œผ๋กœ ๋นŒ๋“œ, ํ…Œ์ŠคํŠธ, ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

์ด ๊ธ€์—์„œ๋Š” GitHub Actions์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๊ณผ ์‚ฌ์šฉ๋ฒ•์„ ์†Œ๊ฐœํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์ง€์†์ ์ธ ํ†ตํ•ฉ(Continuous Integration) -> CI

์ง€์†์ ์ธ ๋ฐฐํฌ(Continuous Delivery or Continuous Deployment) -> CD

 

GitHub Actions๋ž€? ๐Ÿค”

GitHub Actions๋Š” GitHub์—์„œ ์ œ๊ณตํ•˜๋Š” CI/CD ๋„๊ตฌ๋กœ, ๋‹ค์–‘ํ•œ ์ด๋ฒคํŠธ(์˜ˆ: ์ฝ”๋“œ ํ‘ธ์‹œ, PR ์ƒ์„ฑ ๋“ฑ)์— ๋ฐ˜์‘ํ•˜์—ฌ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ํšจ์œจ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ธฐ๋ณธ ๊ตฌ์„ฑ ์š”์†Œ ๐Ÿ› ๏ธ

์›Œํฌํ”Œ๋กœ์šฐ(Workflow): ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ์‹คํ–‰๋˜๋Š” ์ž‘์—…์˜ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. YAML ํ˜•์‹์œผ๋กœ ์ •์˜๋ฉ๋‹ˆ๋‹ค.

์žก(Job): ์›Œํฌํ”Œ๋กœ์šฐ ๋‚ด์—์„œ ์‹คํ–‰๋˜๋Š” ์ž‘์—…์˜ ๋‹จ์œ„์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์žก์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํ…(Step): ์žก ๋‚ด์—์„œ ์‹คํ–‰๋˜๋Š” ๊ฐœ๋ณ„ ๋ช…๋ น์–ด ๋˜๋Š” ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค.

์•ก์…˜(Action): ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ ์กฐ๊ฐ์œผ๋กœ, GitHub Marketplace์—์„œ ๋‹ค์–‘ํ•œ ์•ก์…˜์„ ์ฐพ์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ฐ„๋‹จํ•œ ์›Œํฌํ”Œ๋กœ์šฐ ์˜ˆ์ œ ๐Ÿ“„

๋‹ค์Œ์€ ์ฝ”๋“œ ํ‘ธ์‹œ ์‹œ ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์›Œํฌํ”Œ๋กœ์šฐ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

name: CI

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: ์ฒดํฌ์•„์›ƒ ์ฝ”๋“œ
        uses: actions/checkout@v2

      - name: ๋…ธ๋“œ ์„ค์น˜
        uses: actions/setup-node@v2
        with:
          node-version: '14'

      - name: ์˜์กด์„ฑ ์„ค์น˜
        run: npm install

      - name: ํ…Œ์ŠคํŠธ ์‹คํ–‰
        run: npm test
  • on: ์–ด๋–ค ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ• ์ง€๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” main ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œํ•  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • jobs: ์‹คํ–‰ํ•  ์žก์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • steps: ๊ฐ ์žก์—์„œ ์‹คํ–‰ํ•  ์Šคํ…์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค.

 

Android CI/CD ๊ตฌ์ถ• ๐Ÿซก

Github.Secrets

์™ธ๋ถ€์— ๋…ธ์ถœ๋˜๋ฉด ์•ˆ๋˜๋Š” ์ •๋ณด๋“ค์„ Github์— ์˜ฌ๋ ค๋‘๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋‹ˆ ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด์„œ Github Action ๋™์ž‘ ์ค‘ ๊ฐ’๋“ค์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

LOCAL_PROPERTIES_CONTENT

Local์— ์žˆ๋Š” local.properties๋ฅผ ๋ชจ๋‘ ๋ณต์‚ฌํ•˜์—ฌ ๊ทธ๋Œ€๋กœ ๋„ฃ์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

FIREBASE_APP_ID

Firebase Project -> ํ”„๋กœ์ ํŠธ ์„ค์ • -> ๋‚ด ์•ฑ -> ์•ฑ ID (์•ฑ ๋‹‰๋„ค์ž„ ์œ„์— ์œ„์น˜)๋ฅผ ๋ณต์‚ฌํ•ด์„œ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

GOOGLE_SERVICE_JSON

Firebase Project ์ถ”๊ฐ€ ์‹œ ๋‹ค์šด๋กœ๋“œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” google-services.json์˜ ๋‚ด์šฉ์„ ๋ณต์‚ฌํ•˜์—ฌ ๊ทธ๋Œ€๋กœ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

KEYSTORE_BASE64

keystore.jks๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค๋Š” ๊ฐ€์ •ํ•˜์— ํ•ด๋‹น ํ•ด๋‹น ํŒŒ์ผ์˜ ์œ„์น˜ํ•œ ์žฅ์†Œ์—์„œ ํ„ฐ๋ฏธ๋„์„ ํ†ตํ•ด ์•„๋ž˜ ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋ฉด base64๋กœ ์ธ์ฝ”๋”ฉ๋œ keystore ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๊ฐ’์„ ๋ณต์‚ฌํ•˜์—ฌ ๊ทธ๋Œ€๋กœ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

openssl base64 -in ./keystore.jks -out ./keystore.txt

 

 

CREDENTIAL_FILE_CONTENT

ํ•ด๋‹น ๊ฐ’์€ ์•„๋ž˜ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

ServiceCredentialsFileContent ์ƒ์„ฑ ๋ฐฉ๋ฒ• (Github)

 

FIREBASE_TOKEN migration

This action uploads artifacts (.apk or .ipa) to Firebase App Distribution. - wzieba/Firebase-Distribution-Github-Action

github.com

 

์ตœ์ข… ์ฝ”๋“œ

Firebase Distribution์„ ํ†ตํ•ด ํ…Œ์Šคํ„ฐ์—๊ฒŒ ๋นŒ๋“œ๋œ ์•ฑ์„ ๋น ๋ฅด๊ฒŒ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋กœ,

๋นŒ๋“œ ์‹œ workflow ๋ฒˆํ˜ธ๊ฐ€ App์˜ VersionCode๋กœ ์„ค์ •๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŠน๋ณ„ํ•œ ์ผ์ด ์—†์ด ์—†๋‹ค๋ฉด ๊ณ ์œ ํ•œ ๋ฒ„์ „ ์ฝ”๋“œ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ถ”๊ฐ€๋กœ ํ•ด๋‹น ์ฝ”๋“œ์˜ path๋“ค์€ ๊ฐ ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!

์•„๋ž˜๋Š” Repository ๋ฃจํŠธ์— ํ”„๋กœ์ ํŠธ๊ฐ€ ํด๋” ํ˜•ํƒœ๋กœ ์กด์žฌํ•˜๋Š” ๊ตฌ์กฐ์ž„์„ ๋ง์”€๋“œ๋ฆฝ๋‹ˆ๋‹ค!

name: Versioning, Build And Deployment to Firebase App Distribution
on:
  push:
    branches: [ develop ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      LOCAL_PROPERTIES_CONTENTS: ${{ secrets.LOCAL_PROPERTIES_CONTENTS }}
      GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
      VERSION_CODE: ${{ github.run_number }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'corretto'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x ./3days/gradlew

      - name: Create google-services.json
        run: echo "$GOOGLE_SERVICES_JSON" > ./3days/app/google-services.json

      - name: Create local.properties
        run: echo "$LOCAL_PROPERTIES_CONTENTS" > ./3days/local.properties

      - name: Decode and save keystore
        run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ./3days/app/keystore.jks

      - name: Verify keystore
        run: |
          ls -alh ./3days/app/
          echo "Checking JKS format..."
          keytool -list -v -keystore ./3days/app/keystore.jks -storetype JKS || echo "JKS format verification failed"

      - name: Build release APK
        run: |
          cd ./3days
          ./gradlew assembleRelease -PversionCode=$VERSION_CODE

      - name: Upload to Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          appId: ${{ secrets.FIREBASE_APP_ID }}
          serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
          groups: 3days-tester
          file: ./3days/app/build/outputs/apk/release/3days-release.apk

 

ํŠธ๋ฆฌ๊ฑฐ๋Š” develop ๋ธŒ๋žœ์น˜์— push ํ–ˆ์„ ๋•Œ ์ˆ˜ํ–‰.

+ ์ˆ˜๋™์œผ๋กœ ๋™์ž‘์‹œํ‚ค๊ธฐ ์œ„ํ•œ workflow_dispatch

workflow_dispatch๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด Github Action์—์„œ develop ๋ธŒ๋žœ์น˜์— push ํ•˜์ง€ ์•Š๋”๋ผ๋„ ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ

 

์šฐ์„  ์ˆœ์„œ๋ฅผ ์ •๋ฆฌํ•˜์ž๋ฉด,

1. ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ

2. JDK Setup

3. gradlew ๊ถŒํ•œ ํ—ˆ์šฉ

4. google-services.json ํŒŒ์ผ ์ƒ์„ฑ

5. local.properties ํŒŒ์ผ ์ƒ์„ฑ

6. keystore ๋””์ฝ”๋“œ & ํŒŒ์ผ ์ƒ์„ฑ

7. ์ƒ์„ฑ๋œ keystore ํŒŒ์ผ verify (๊ฐ€๋” ์ธ์ฝ”๋”ฉ์„ ์ž˜๋ชปํ•˜๊ณ  ์ง„ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด์„œ ํ™•์ธ์„ ์œ„ํ•ด ์ถ”๊ฐ€, ํฌ๊ฒŒ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์Œ)

8. APK ๋นŒ๋“œ

9. Firebae Distribution

 

์—ฌ๊ธฐ์— test, lint check ๋“ฑ์˜ ์ž‘์—…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

build.gradle(app)

val properties = gradleLocalProperties(rootDir, providers)

android {
    ...
    defaultConfig {
        ...
        // VersionCode = workflow Number
        versionCode = if (project.hasProperty("versionCode")) {
            project.property("versionCode").toString().toInt()
        } else {
            1
        }
    }

    // APK ํŒŒ์ผ๋ช… ์„ค์ •
    applicationVariants.all {
        outputs.all {
            val outputImpl = this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
            val newFileName = "3days-${name}.apk"
            outputImpl.outputFileName = newFileName
        }
    }

    // Signing Configs ์„ค์ •
    signingConfigs {
        create("release") {
            keyAlias = properties["SIGNED_KEY_ALIAS"] as String?
            keyPassword = properties["SIGNED_KEY_PASSWORD"] as String?
            storeFile = properties["SIGNED_STORE_FILE"]?.let { file(it) }
            storePassword = properties["SIGNED_STORE_PASSWORD"] as String?
        }
    }

    // Build Types ์„ค์ •
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            ...
        }
    }
    buildFeatures {
        buildConfig = true
    }
    ...
}

 

Path๋“ค๋งŒ ์ž์‹ ์˜ ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ž˜ ์ˆ˜์ •ํ•œ๋‹ค๋ฉด ์‰ฝ๊ฒŒ CI/CD ๊ตฌ์ถ•์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค!

 

๐ŸŽ‰ ๊ฒฐ๋ก 

์ด์ œ GitHub develop Branch์— ์ฝ”๋“œ๋ฅผ ํ‘ธ์‹œํ•˜๋ฉด, GitHub Actions๊ฐ€ ์ž๋™์œผ๋กœ ๋นŒ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  Firebase Distribution์„ ํ†ตํ•ด ํ…Œ์Šคํ„ฐ์—๊ฒŒ APK๋ฅผ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค. CI/CD๋ฅผ ํ†ตํ•ด ํšจ์œจ์ ์ธ ๊ฐœ๋ฐœ๊ณผ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ๐Ÿš€

 

 

'[Etc.] > Git' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Git] Github Labels ํ•œ ๋ฒˆ์— ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•  (0) 2024.11.27
[Git] CHANGELOG.md Generate  (0) 2024.10.26
[Git] AngularJS Commit Conventions  (0) 2024.10.26