CI/CD - GitHub Actions, Codemagic
์ง๋ ๊ธ์์ ์คํ ์ด์ ์ฌ๋ฆฌ๊ธฐ ์ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ๋ง๋ค์๋ค. ๋ถ์ ์๋ฌ๋ ์๋์ง, ํ ์คํธ๋ ํต๊ณผํ๋์ง, ์๋ฒ ์ค์ ์ ์ด์์ฉ์ธ์ง, ๋น๋ ๋ฒํธ๋ ์ฌ๋ ธ๋์ง. ์ด๊ฑธ ์ฌ๋์ด ๋งค๋ฒ ๊ธฐ์ตํด์ ํ๋ค๋ฉด ์ธ์ ๊ฐ๋ ๋น ๋จ๋ฆฐ๋ค. ์๋ฅผ ๋ค์ด ์ด๋ฐ ์ผ์ด ์๊ธด๋ค.
- ๊ธํ๊ฒ ๊ณ ์ณ์ ์ฌ๋ฆฌ๋๋ผ
flutter test๋ฅผ ์ ๋๋ ธ๋๋ฐ, ๋ค๋ฅธ ํ๋ฉด์ด ๊นจ์ง ์ฑ๋ก ๋ฐฐํฌ๋๋ค - ๋ด ์ปดํจํฐ์์๋ ๋น๋๊ฐ ๋๋๋ฐ, ํ์ ์ปดํจํฐ์์๋ Flutter ๋ฒ์ ์ด ๋ฌ๋ผ์ ์ ๋๋ค
- ์๋ช ํค๊ฐ ํ ์ฌ๋์ ๋ ธํธ๋ถ์๋ง ์์ด์, ๊ทธ ์ฌ๋์ด ์์ผ๋ฉด ๋ฐฐํฌ๋ฅผ ๋ชป ํ๋ค
์ฌ๋์ด ํ๋ ์ผ์ ์ฝ๋๋ก ์ ์ด์ ์๋ฒ๊ฐ ๋์ ํ๊ฒ ํ๋ ๊ฒ์ด CI/CD๋ค.
- CI(์ง์์ ํตํฉ): ์ฝ๋๋ฅผ ์ฌ๋ฆด ๋๋ง๋ค ์๋์ผ๋ก ๋ถ์, ํ ์คํธ, ๋น๋๋ฅผ ๋๋ ค์ ๊นจ์ก๋์ง ์๋ ค์ค๋ค
- CD(์ง์์ ๋ฐฐํฌ): ๋น๋๋ ํ์ผ์ ์๋์ผ๋ก ํ ์คํธ ํธ๋์ด๋ ์คํ ์ด์ ์ฌ๋ฆฐ๋ค
1๋จ๊ณ: GitHub Actions๋ก CI ๋ง๋ค๊ธฐ
GitHub ์ ์ฅ์๋ฅผ ์ฐ๊ณ ์๋ค๋ฉด ๊ฐ์ฅ ์ฝ๊ฒ ์์ํ ์ ์๋ค. ์ ์ฅ์์ .github/workflows/ ํด๋์ ci.yml ํ์ผ์ ๋ง๋ค๋ฉด ๋๋ค.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- run: flutter pub get
- run: flutter analyze
- run: flutter test๊ตฌ์กฐ๋ฅผ ์์์๋ถํฐ ์ฝ์ด๋ณด์.
on: ์ธ์ ์คํํ ์ง๋ค.main์ ์ฝ๋๊ฐ ์ฌ๋ผ์ฌ ๋(push)์main์ผ๋ก ํฉ์น๋ ค๋ ์์ฒญ(pull_request)์ด ์ฌ๋ผ์ฌ ๋ ์คํ๋๋คjobs: ํ ์ผ์ ๋ฌถ์์ด๋ค. ์ฌ๊ธฐ์๋testํ๋๋คruns-on: ubuntu-latest: ์ผ์ ์ํฌ ๊ฐ์ ์ปดํจํฐ๋ค. GitHub์ด ๋น๋ ค์ฃผ๋ ๋ฆฌ๋ ์ค๋คsteps: ์์๋๋ก ์คํํ ๋จ๊ณ๋ค.uses๋ ๋จ์ด ๋ง๋ค์ด ๋ ์ก์ ์ ๊ฐ์ ธ๋ค ์ฐ๋ ๊ฒ์ด๊ณ ,run์ ํฐ๋ฏธ๋ ๋ช ๋ น์ ๊ทธ๋๋ก ์คํํ๋ ๊ฒ์ด๋คactions/checkout: ์ ์ฅ์ ์ฝ๋๋ฅผ ๊ฐ์ ์ปดํจํฐ๋ก ๋ด๋ ค๋ฐ๋๋คsubosito/flutter-action: ๊ฐ์ ์ปดํจํฐ์ Flutter๋ฅผ ์ค์นํ๋ค.cache: true๋ ๋ค์ ์คํ ๋ ๋ค์ ๋ด๋ ค๋ฐ์ง ์๊ฒ ํด์ ๋น ๋ฅด๋คflutter analyze,flutter test: ๋ก์ปฌ์์ ํ๋ ๊ทธ ๋ช ๋ น์ด๋ค
์ด ํ์ผ์ ์ปค๋ฐํด์ ์ฌ๋ฆฌ๋ฉด GitHub ์ ์ฅ์์ Actions ํญ์์ ์คํ์ด ๋ณด์ด๊ณ , ์คํจํ๋ฉด ๋นจ๊ฐ ํ์๊ฐ ๋ฌ๋ค. ํ ๋ฆฌํ์คํธ์๋ ํต๊ณผ ์ฌ๋ถ๊ฐ ๋ถ์ด์, ํ ์คํธ๊ฐ ๊นจ์ง ์ฝ๋๊ฐ ํฉ์ณ์ง๋ ๊ฑธ ๋ง์ ์ ์๋ค. ์ ์ฅ์ ์ค์ ์ ๋ธ๋์น ๋ณดํธ ๊ท์น์์ "์ด ๊ฒ์ฌ๋ฅผ ํต๊ณผํด์ผ ํฉ์น ์ ์๋ค"๋ฅผ ์ผ๋ฉด ๊ฐ์ ๋๋ค.
์ก์
์ ๋ฒ์ (@v4, @v2)์ ์๊ฐ์ด ์ง๋๋ฉด ์ ๋ฒ์ ์ด ๋์ค๋, ์ ์ ๋ ๊ณต์ ์ ์ฅ์์์ ์ต์ ์ ํ์ธํ๋ค.
์ฝ์ง ํฌ์ธํธ 1: ๋ด ์ปดํจํฐ์์๋ ๋๋๋ฐ CI์์๋ง ์คํจํ๋ค
CI๋ ๊นจ๋ํ ์ ์ปดํจํฐ์์ ์ฒ์๋ถํฐ ์์ํด์, ๋ด ์ปดํจํฐ์ ์์ฌ ์๋ ๊ฒ๋ค์ด ์๋ค. ํํ ์์ธ์ ์ด๋ ๋ค.
- Flutter ๋ฒ์ ์ด ๋ค๋ฅด๋ค: ์ค์ ์
channel: stable๋ง ๋๋ฉด ํญ์ ์ต์ ์ด ์ค์น๋๋ค. ํ์ ๋ก์ปฌ ๋ฒ์ ๊ณผ ๋ง์ถ๋ ค๋ฉดflutter-version: '๋ฒ์ '์ผ๋ก ๊ณ ์ ํ๋ค - ์์ฑ๋ ์ฝ๋๊ฐ ์๋ค: JSON ๊ธ์
*.g.dart,*.freezed.dart๋ ๋ค๊ตญ์ด ๊ธ์AppLocalizations๋ฅผ ์ ์ฅ์์์ ์ ์ธ(.gitignore)ํ๋ค๋ฉด, CI์์๋ ํ์ผ์ด ์์ด์ "์์ฑ๋์ง ์์๋ค"๋ ์ทจ์ง์ ์๋ฌ๊ฐ ๋๋ค. ๋ถ์ ์ ์ ์์ฑ ๋ช ๋ น์ ๋ฃ๋๋ค
- run: flutter pub get
- run: dart run build_runner build --delete-conflicting-outputs
- run: flutter gen-l10n
- run: flutter analyze
- run: flutter test- ํ์ผ ์ด๋ฆ์ ๋์๋ฌธ์: macOS๋
User.dart์user.dart๋ฅผ ๊ฐ์ ํ์ผ๋ก ๋ณด์ง๋ง, ๋ฆฌ๋ ์ค๋ ๋ค๋ฅธ ํ์ผ๋ก ๋ณธ๋ค. import์ ๋์๋ฌธ์๊ฐ ์ด๊ธ๋ ์์ผ๋ฉด ๋ก์ปฌ์์๋ ๋๊ณ CI์์๋ง ์คํจํ๋ค
2๋จ๊ณ: ์๋ช ํ ์ฑ ๋ฒ๋ค๊น์ง ๋น๋ํ๊ธฐ
CI๊ฐ ํต๊ณผํ๋ฉด ๋ฐฐํฌ์ฉ ํ์ผ๋ ๋ง๋ค ์ ์๋ค. ๋ฌธ์ ๋ ์๋ช
ํค๋ค. ์ง๋ ๊ธ์์ ํค์คํ ์ด์ key.properties๋ ์ ์ฅ์์ ์ฌ๋ฆฌ๋ฉด ์ ๋๋ค๊ณ ํ๋ค. ๊ทธ๋ผ CI๋ ์๋ช
์ ์ด๋ป๊ฒ ํ ๊น? GitHub Secrets๋ฅผ ์ด๋ค. ์ ์ฅ์ ์ค์ ์ Settings โ Secrets and variables โ Actions์ ๊ฐ์ ์ ์ฅํด ๋๊ณ , ์คํํ ๋๋ง ๊บผ๋ด ์ด๋ค.
ํ์ผ์ Secrets์ ์ง์ ๋ชป ๋ฃ์ผ๋ base64 ๋ฌธ์์ด๋ก ๋ฐ๊ฟ์ ์ ์ฅํ๋ค.
# ๋ฆฌ๋
์ค
base64 -w 0 upload-keystore.jks > keystore.txt
# macOS
base64 -b 0 -i upload-keystore.jks > keystore.txtkeystore.txt์ ๋ด์ฉ์ KEYSTORE_BASE64๋ผ๋ Secret์ผ๋ก ์ ์ฅํ๊ณ , ๋น๋ฐ๋ฒํธ์ ๋ณ์นญ๋ ๊ฐ๊ฐ KEYSTORE_PASSWORD, KEY_PASSWORD, KEY_ALIAS๋ก ์ ์ฅํ๋ค. ๊ทธ๋ค์ ์ํฌํ๋ก์์ ๋ณต์ํ๋ค.
build-android:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- uses: subosito/flutter-action@v2
with:
channel: stable
- run: flutter pub get
- name: ํค์คํ ์ด์ key.properties ๋ณต์
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/upload-keystore.jks
echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" > android/key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
echo "storeFile=upload-keystore.jks" >> android/key.properties
- run: >
flutter build appbundle --release
--build-number=${{ github.run_number }}
--dart-define=FLAVOR=prod
- uses: actions/upload-artifact@v4
with:
name: release-aab
path: build/app/outputs/bundle/release/*.aabneeds: test:test์์ ์ด ์ฑ๊ณตํด์ผ ์ด ์์ ์ ์์ํ๋ค. ํ ์คํธ๊ฐ ๊นจ์ง๋ฉด ๋ฐฐํฌ์ฉ ๋น๋๋ ์์ ๋ง๋ค์ง ์๋๋คactions/setup-java: ์๋๋ก์ด๋ ๋น๋์ ํ์ํ ์๋ฐ๋ฅผ ์ค์นํ๋ค. ๋ฒ์ ์ ํ๋ก์ ํธ์ Gradle์ด ์๊ตฌํ๋ ๊ฒ์ ๋ง์ถ๋ค- ๋ณต์ ๋จ๊ณ: Secrets์์ ๊บผ๋ธ ๊ฐ์ผ๋ก ํค์คํ ์ด ํ์ผ๊ณผ
key.properties๋ฅผ ๊ทธ ์๋ฆฌ์์๋ง ๋ง๋ ๋ค. ๊ฐ์ ์ปดํจํฐ๋ ์คํ์ด ๋๋๋ฉด ์ฌ๋ผ์ง๋ฏ๋ก ํค๊ฐ ๋จ์ง ์๋๋ค --build-number=${{ github.run_number }}: ์คํํ ๋๋ง๋ค ์ฌ๋ผ๊ฐ๋ ๋ฒํธ๋ฅผ ๋น๋ ๋ฒํธ๋ก ์ด๋ค. ์ง๋ ๊ธ์ "๊ฐ์ ๋ฒํธ๋ฅผ ๋ ๋ฒ ์ฌ๋ฆด ์ ์๋ค"๋ฅผ ์ฌ๋์ด ์๋๋ผ CI๊ฐ ํด๊ฒฐํด ์ค๋ค--dart-define=FLAVOR=prod: flavor ๊ธ์ ๊ฐ์ ๋๊ธด๋ค. flavor๋ฅผ ์ด๋ค๋ฉด--flavor prod๋ ๋ถ์ธ๋คactions/upload-artifact: ๋ง๋ค์ด์ง.aab๋ฅผ ์คํ ๊ฒฐ๊ณผ์ ์ฒจ๋ถ ํ์ผ๋ก ๋จ๊ฒจ์, Actions ํญ์์ ๋ด๋ ค๋ฐ์ ์ ์๋ค
์ฝ์ง ํฌ์ธํธ 2: ๋น๋ ๋ฒํธ๊ฐ ์ด๋ฏธ ์ฌ๋ฆฐ ๊ฒ๋ณด๋ค ์๋ค
github.run_number๋ 1๋ถํฐ ์์ํ๋ค. ์ด๋ฏธ Play Console์ 1.0.0+20๊น์ง ์ฌ๋ ค ๋ ์ฑ์ด๋ผ๋ฉด, CI๊ฐ ๋ง๋ ๋ฒํธ 5๋ ์ด๋ฏธ ์ด ๋ฒํธ๋ณด๋ค ์์์ ์
๋ก๋๊ฐ ๊ฑฐ๋ถ๋๋ค. ํ์ฌ ๋ฒํธ๋ณด๋ค ํฐ ๊ฐ๋ถํฐ ์์ํ๋๋ก ๋ํด์ค๋ค.
--build-number=$(( ${{ github.run_number }} + 200 ))์ฝ์ง ํฌ์ธํธ 3: ๋น๋ฐ ๊ฐ์ด ์์ง ์๊ฒ ํ๋ค
.jks,keystore.txt,key.properties๋ ์ ์ฅ์์ ์ฌ๋ฆฌ์ง ์๋๋ค..gitignore์ ๋ฃ์ด์ ์ค์๋ก ์ปค๋ฐ๋์ง ์๊ฒ ํ๋ค- Secrets๋ ์คํ ๋ก๊ทธ์์
***๋ก ๊ฐ๋ ค์ง์ง๋ง, ์ผ๋ถ๋ฌecho๋ก ์ฐ๊ฑฐ๋ ๋ณํํด์ ์ถ๋ ฅํ์ง ์๋๋ค - ์ธ๋ถ ์ฌ๋์ด ์ฌ๋ฆฐ ํฌํฌ์ ํ ๋ฆฌํ์คํธ์๋ Secrets๊ฐ ์ ๋ฌ๋์ง ์๋๋ค. ์๋ช
์ด ํ์ํ ๋น๋ ์์
์
main๋ธ๋์น๋ ํ๊ทธ์์๋ง ๋๋ฆฌ๋๋กif๋ก ๋ง์๋๋ค
3๋จ๊ณ: ํ๊ทธ๋ฅผ ๋ฌ๋ฉด ๋ฐฐํฌ๋๊ฒ ํ๊ธฐ
main์ ์ฌ๋ฆด ๋๋ง๋ค ์คํ ์ด์ ์ฌ๋ฆฌ๋ ๊ฑด ์ํํ๋ค. ๋ฐฐํฌ๋ ๋ด๊ฐ ์ ํ ์๊ฐ์ ํ๊ฒ ํ๋ ค๋ฉด ํ๊ทธ๋ฅผ ํธ๋ฆฌ๊ฑฐ๋ก ์ด๋ค.
on:
push:
tags: ['v*'] # v1.0.0, v1.0.1 ๊ฐ์ ํ๊ทธ๋ฅผ ์ฌ๋ฆฌ๋ฉด ์คํgit tag v1.0.1
git push origin v1.0.1ํ๊ทธ๋ฅผ ์ฌ๋ฆฌ๋ ์๊ฐ๋ง ๋ฐฐํฌ ์์
์ด ๋๊ณ , ํ์ main์ ์ฌ๋ฆฌ๋ ์ฝ๋๋ CI(๋ถ์, ํ
์คํธ)๋ง ๋๋ค.
4๋จ๊ณ: Google Play ๋ด๋ถ ํ ์คํธ์ ์๋์ผ๋ก ์ฌ๋ฆฌ๊ธฐ
๋น๋๋ .aab๋ฅผ Play Console์ ์ฌ๋ฆฌ๋ ๋จ๊ณ๋ ์๋์ผ๋ก ํ ์ ์๋ค. Google Play API๋ฅผ ์ฐ๋ ์๋น์ค ๊ณ์ ์ JSON ํค๋ฅผ Secret์ผ๋ก ์ ์ฅํ๊ณ , ์
๋ก๋์ฉ ์ก์
์ ์ด๋ค.
- uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_JSON }}
packageName: com.example.myapp
releaseFiles: build/app/outputs/bundle/release/app-release.aab
track: internaltrack: internal์ ์ง๋ ๊ธ์์ ๊ถํ ๋ด๋ถ ํ
์คํธ๋ถํฐ ์ฌ๋ฆฌ๋ผ๊ณ ํ ๊ทธ ํธ๋์ด๋ค. ์ก์
์ ์
๋ ฅ ์ด๋ฆ(releaseFiles ๋ฑ)์ ์ก์
๋ฒ์ ์ ๋ฐ๋ผ ๋ค๋ฅด๋ ์ฌ์ฉํ๋ ์ก์
์ ์ค๋ช
์๋ฅผ ํ์ธํ๋ค. ๋ Play Console์ ์ฑ์ ์ฒซ ๋ฒ์งธ ์ถ์๋ ์ง์ ์ฌ๋ ค์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ด์, ์๋ํ๋ ์ฒซ ์
๋ก๋๋ฅผ ๋ง์น ๋ค๋ถํฐ ์ด๋ค. ์๋น์ค ๊ณ์ ์ ๋ง๋ค๊ณ Play Console์ ๊ถํ์ ์ฃผ๋ ๊ณผ์ ์ Google์ ์๋ด๋ฅผ ๋ฐ๋ผ ํ ๋ฒ ์ค์ ํด ๋๋ค.
iOS๋ ๋ ๊น๋ค๋กญ๋ค
iOS ๋น๋๋ Mac์์๋ง ๊ฐ๋ฅํด์ runs-on: macos-latest๋ฅผ ์จ์ผ ํ๊ณ , ๋ฆฌ๋
์ค๋ณด๋ค ์คํ ๋น์ฉ์ด ๋ ๋ ๋ค. ์๋ช
๋ ๋ณต์กํ๋ค. ์ธ์ฆ์(.p12)์ ํ๋ก๋น์ ๋ ํ๋กํ์ผ, App Store Connect API ํค๋ฅผ ๋ชจ๋ Secrets์ base64๋ก ๋ฃ๊ณ , ์คํ ์ค์ ์์ ํค์ฒด์ธ์ ๋ง๋ค์ด ๋ฑ๋กํ๋ ๋จ๊ณ๊ฐ ํ์ํ๋ค.
build-ios:
runs-on: macos-latest
steps:
# ... checkout, flutter ์ค์น, pub get
# ์ธ์ฆ์, ํ๋กํ์ผ ๋ณต์, ํค์ฒด์ธ ์ค์ (๋จ๊ณ๊ฐ ๋ง๋ค)
- run: flutter build ipa --release --build-number=${{ github.run_number }}์ด ๊ณผ์ ์ ์ง์ ๋ง๋ค๊ธฐ๋ณด๋ค Fastlane ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฐ๊ฑฐ๋, ์๋์ Codemagic์ฒ๋ผ ์๋ช ์ ๋์ ์ฒ๋ฆฌํด ์ฃผ๋ ์๋น์ค๋ฅผ ์ฐ๋ ์ฌ๋์ด ๋ง๋ค.
Codemagic: Flutter์ ๋ง์ถ CI/CD ์๋น์ค
Codemagic์ Flutter ์ฑ์ ์ํ CI/CD ์๋น์ค๋ค. Mac ๋น๋ ๋จธ์ ์ด ๊ธฐ๋ณธ์ผ๋ก ์๊ณ , iOS ์๋ช
๊ณผ ์คํ ์ด ์
๋ก๋๋ฅผ ์ค์ ๋ช ์ค๋ก ์ฒ๋ฆฌํด ์ค๋ค. ์ค์ ์ ์น ํ๋ฉด์ ์ํฌํ๋ก ํธ์ง๊ธฐ๋ก๋ ํ ์ ์๊ณ , ์ ์ฅ์ ๋งจ ์์ codemagic.yaml ํ์ผ์ ๋๋ ๋ฐฉ์๋ ์๋ค. ํ์ผ๋ก ๋๋ฉด ์ค์ ์ด ์ฝ๋์ ํจ๊ป ๋ฒ์ ๊ด๋ฆฌ๋์ด์ ์ด ๊ธ์์๋ YAML์ ์ด๋ค.
workflows:
android-workflow:
name: Android Workflow
instance_type: linux_x2
max_build_duration: 60
environment:
groups:
- google_play # GCLOUD_SERVICE_ACCOUNT_CREDENTIALS๊ฐ ๋ค์ด ์๋ ๋ณ์ ๊ทธ๋ฃน
android_signing:
- upload_key # Codemagic์ ์ฌ๋ ค๋ ํค์คํ ์ด ์ฐธ์กฐ ์ด๋ฆ
flutter: stable
scripts:
- name: Get Flutter packages
script: flutter packages pub get
- name: Flutter analyze
script: flutter analyze
- name: Flutter unit tests
script: flutter test
- name: Build AAB
script: flutter build appbundle --release --build-number=$PROJECT_BUILD_NUMBER
artifacts:
- build/**/outputs/**/*.aab
publishing:
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internal
ios-workflow:
name: iOS Workflow
instance_type: mac_mini_m2
integrations:
app_store_connect: codemagic
environment:
ios_signing:
distribution_type: app_store
bundle_identifier: com.example.myapp
flutter: stable
scripts:
- name: Set up code signing
script: xcode-project use-profiles
- name: Get Flutter packages
script: flutter packages pub get
- name: Build IPA
script: |
flutter build ipa --release \
--build-number=$PROJECT_BUILD_NUMBER \
--export-options-plist=/Users/builder/export_options.plist
artifacts:
- build/ios/ipa/*.ipa
publishing:
app_store_connect:
auth: integration
submit_to_testflight: trueGitHub Actions์ ์ฝ๋ ๋ฒ์ด ๊ฐ๋ค. scripts๋ run๊ณผ ๊ฐ๊ณ , artifacts๋ ๊ฒฐ๊ณผ ํ์ผ์ด๋ค. ๋ฌ๋ผ์ง ์ ์ ์ด๋ ๋ค.
workflows์๋์ ํ๋ซํผ๋ณ ์ํฌํ๋ก๋ฅผ ์ด๋ฆ ๋ถ์ฌ ๋๋ค. ์๋๋ก์ด๋๋ ๋ฆฌ๋ ์ค ๋จธ์ , iOS๋ Mac ๋จธ์ (mac_mini_m2)์์ ๋๋ฆฐ๋คandroid_signing,ios_signing: ์๋ช ํค๋ฅผ Codemagic์ ์ฌ๋ ค ๋๊ณ ์ด๋ฆ์ผ๋ก ์ฐธ์กฐํ๋ค. ํค์คํ ์ด๋ฅผ base64๋ก ํ์ด์key.properties๋ฅผ ์ฐ๋ ๋จ๊ณ๊ฐ ์์ด์ง๋ค. iOS๋xcode-project use-profilesํ ์ค๋ก ์๋ช ์ค์ ์ด ๋๋๋ค$PROJECT_BUILD_NUMBER: Codemagic์ด ๋น๋๋ง๋ค ์ฌ๋ ค์ฃผ๋ ๋ฒํธ๋คpublishing: ๋น๋๊ฐ ๋๋๋ฉด Google Play์ App Store Connect(TestFlight)๋ก ๋ฐ๋ก ์ ๋ก๋ํ๋ค
instance_type์ ์ด๋ฆ, auth, submit_to_testflight ๊ฐ์ ๊ฒ์ ์ต์
๊ณผ ๋ฌด๋ฃ ์ฌ์ฉ๋ ์ ์ฑ
์ Codemagic ์ชฝ์์ ๋ฐ๋ ์ ์์ผ๋, ์ค์ ํ๊ธฐ ์ ์ Codemagic ๋ฌธ์์ ํ์ฌ ์์๋ฅผ ํ์ธํ๋ค.
GitHub Actions์ Codemagic, ๋ญ ์ฐ๋
- GitHub Actions: ์ ์ฅ์๊ฐ GitHub์ ์๋ค๋ฉด ๋ฐ๋ก ๊ฐ์ ์์ด ์์ํ๋ค. ๋ถ์๊ณผ ํ ์คํธ(CI)๊ฐ ์ฃผ๋ชฉ์ ์ด๊ณ ์๋๋ก์ด๋ ์์ฃผ๋ผ๋ฉด ์ถฉ๋ถํ๋ค. iOS ์๋ช ๊ณผ ๋ฐฐํฌ๋ ์ง์ ๊ตฌ์ฑํด์ผ ํ๋ค
- Codemagic: iOS์ ์๋๋ก์ด๋๋ฅผ ๊ฐ์ด ๋ฐฐํฌํ๋ Flutter ์ฑ์ ์๋ง๋ค. ์๋ช ๊ณผ ์คํ ์ด ์ ๋ก๋๊ฐ ์ฝ๋ค. ์ธ๋ถ ์๋น์ค์ ํค๋ฅผ ๋งก๊ธฐ๋ ์ ๊ณผ ์ฌ์ฉ๋ ํ๋๋ ๊ฐ์ํ๋ค
๋์ ์๋ ๋ฐฉ๋ฒ๋ ํํ๋ค. ๋ถ์๊ณผ ํ ์คํธ๋ ํ ๋ฆฌํ์คํธ๋ง๋ค GitHub Actions๊ฐ, ์คํ ์ด ๋ฐฐํฌ๋ ํ๊ทธ๋ฅผ ๋ฌ ๋ Codemagic์ด ๋งก๋๋ค.
์ฒ์๋ถํฐ ๋ค ๋ง๋ค์ง ์๋๋ค
CI/CD๋ ํ ๋ฒ์ ์์ฑํ๋ ค๊ณ ํ๋ฉด ์ง์น๊ธฐ ์ฝ๋ค. ์ด ์์๋ก ํ ๋จ๊ณ์ฉ ์ฌ๋ฆฐ๋ค.
- 1๋จ๊ณ:
flutter analyze์flutter test๋ง ์๋์ผ๋ก (์ด ๊ธ์ 1๋จ๊ณ) - 2๋จ๊ณ: ์๋ช
ํ
.aab๋ฅผ ๋ง๋ค์ด์ ํ์ผ๋ก ๋จ๊ธฐ๊ธฐ - 3๋จ๊ณ: ํ๊ทธ๋ฅผ ๋ฌ๋ฉด ๋ด๋ถ ํ ์คํธ ํธ๋์ ์ฌ๋ฆฌ๊ธฐ
- 4๋จ๊ณ: iOS๋ TestFlight๊น์ง ์๋์ผ๋ก
Android, iOS์ ๋น๊ตํ๋ฉด
- ์๋๋ก์ด๋ ๋ค์ดํฐ๋ธ์ CI๋
./gradlew test,./gradlew bundleRelease๋ฅผ ์คํํ๋ค. Flutter์์๋ ๊ทธ ์๋ฆฌ์flutter test,flutter build appbundle์ด ๋ค์ด๊ฐ๋ค - iOS ๋ค์ดํฐ๋ธ์ CI๋
xcodebuild๋ Fastlane์ ์ด๋ค. Flutter๋flutter build ipa๊ฐ ๊ทธ ์ญํ ์ ํ๊ณ , ์๋ช ๊ณผ ์ ๋ก๋์์๋ Fastlane๋ ๊ทธ๋๋ก ์ธ ์ ์๋ค - GitHub Actions, Jenkins ๊ฐ์ CI ์๋น์ค ์์ฒด๋ ๋ค์ดํฐ๋ธ ์ฑ๊ณผ ๋๊ฐ์ด ์ฐ๋ ๋๊ตฌ๋ค
์ด๋ฏธ ๋ค์ดํฐ๋ธ ์ฑ์ CI๋ฅผ ๊ตฌ์ฑํด ๋ณธ ๊ฒฝํ์ด ์๋ค๋ฉด ๊ตฌ์กฐ๋ ๊ทธ๋๋ก๋ค. Flutter์์๋ ๋น๋ ๋ช
๋ น์ด flutter ...๋ก ๋ฐ๋๊ณ , ์คํ๊ธฐ์ Flutter๋ฅผ ์ค์นํ๋ ๋จ๊ณ๊ฐ ํ๋ ๋์ด๋๋ค๋ ์ ๋๊ฐ ์ฐจ์ด๋ค.
์ ๋ฆฌ
- ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์ฌ๋์ด ๊ธฐ์ตํด์ ํ๋ฉด ์ธ์ ๊ฐ ๋น ๋จ๋ฆฌ๋ฏ๋ก, ์ฝ๋๋ฅผ ์ฌ๋ฆด ๋๋ง๋ค ๋ถ์, ํ ์คํธ, ๋น๋๋ฅผ ์๋์ผ๋ก ๋๋ฆฌ๋ CI์ ๊ฒฐ๊ณผ๋ฅผ ์คํ ์ด๋ก ๋ณด๋ด๋ CD๋ฅผ ๊ตฌ์ฑํ๋ค
- GitHub Actions๋
.github/workflows/*.yml์on(์ธ์ ),jobs์steps(๋ฌด์์)๋ฅผ ์ ๊ณ ,subosito/flutter-action์ผ๋ก Flutter๋ฅผ ์ค์นํดflutter analyze,flutter test๋ฅผ ์คํํ๋ค - ๋ก์ปฌ์์๋ ๋๊ณ CI์์๋ง ์คํจํ๋ฉด Flutter ๋ฒ์ ๊ณ ์ , ์์ฑ ์ฝ๋(
build_runner,gen-l10n) ์คํ, ํ์ผ ์ด๋ฆ์ ๋์๋ฌธ์๋ฅผ ํ์ธํ๋ค - ์๋ช
ํค๋ ์ ์ฅ์์ ์ฌ๋ฆฌ์ง ์๊ณ base64๋ก ๋ฐ๊ฟ GitHub Secrets์ ์ ์ฅํ ๋ค ๋น๋ ์ค์๋ง ๋ณต์ํ๋ฉฐ, ๋น๋ ๋ฒํธ๋
github.run_number๋ก ์ฌ๋ฆฌ๋ ์ด๋ฏธ ์ฌ๋ฆฐ ๋ฒํธ๋ณด๋ค ํฐ ๊ฐ๋ถํฐ ์์ํ๊ฒ ํ๋ค - ๋ฐฐํฌ๋ ํ๊ทธ(
v*)๋ฅผ ํธ๋ฆฌ๊ฑฐ๋ก ๋๊ณ ๋ด๋ถ ํ ์คํธ ํธ๋(track: internal)๋ถํฐ ์ฌ๋ฆฌ๋ฉฐ, Play ์ ๋ก๋๋ ์ฑ์ ์ฒซ ์ถ์๋ฅผ ์ง์ ์ฌ๋ฆฐ ๋ค๋ถํฐ ์๋ํํ๋ค - iOS๋ Mac ์คํ๊ธฐ์ ๋ณต์กํ ์๋ช
๋๋ฌธ์ Fastlane์ด๋ Codemagic(
codemagic.yaml์ios_signing,publishing)์ ๋ง์ด ์ด๋ค - ๋ถ์๊ณผ ํ ์คํธ๋ถํฐ ์์ํด์ ๋น๋, ๋ฐฐํฌ ์์ผ๋ก ํ ๋จ๊ณ์ฉ ์๋ํ๋ฅผ ๋๋ฆฐ๋ค
์ด ๊ธ๋ก Flutter ์๋ฆฌ์ฆ 37ํธ์ด ๋๋ฌ๋ค. Dart์ final๊ณผ late์์ ์์ํด์, ์์ ฏ๊ณผ ์ํ ๊ด๋ฆฌ, ๋ฐ์ดํฐ์ ๊ตฌ์กฐ, ํ
์คํธ์ ๋ฐฐํฌ ์๋ํ๊น์ง ํ ๋ฐํด๋ฅผ ๋์๋ค. ๋ค์์๋ ์ด ํ๋ฆ์ ํ ์ฑ์ ์ฒ์๋ถํฐ ๋๊น์ง ์ ์ฉํด ๋ณด๋ ์ค์ต ๊ธ๋ก ์ด์ด๊ฐ๊ฒ ๋ค.
