-
Notifications
You must be signed in to change notification settings - Fork 0
[SETTING/#34] fastlane 릴리즈 자동화 — 테스트·테스터배포(Firebase)·Play배포(supply) lane #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e1aa04b
[setting/#34] fastlane 기본 구조 추가 (Gemfile·Appfile·Pluginfile)
oilbeaneda 03ff0b3
[setting/#34] Firebase App Distribution 배포 lane(beta)·테스트 lane 작성
oilbeaneda 80da332
[setting/#34] fastlane 사용 가이드(README) 추가 + .gitignore 정리
oilbeaneda 6eb4b79
[setting/#34] Gemfile.lock 고정 + 로컬 설치(vendor/bundle) 가이드·권한 이슈 해결법 추가
oilbeaneda 00f7966
[setting/#34] beta 배포 검증 반영 — 그룹 alias(모여볼) 기본값·skip_docs·UTF-8/트러블슈팅…
oilbeaneda e69c1dc
[setting/#34] 릴리즈 서명 설정 배선 (local.properties/env의 upload keystore)
oilbeaneda 66cbfcc
[setting/#34] Google Play 배포 lane(deploy, supply) 추가 + 세팅 가이드
oilbeaneda 98af40d
[fix/#34] 릴리즈 서명값 4개+keystore 파일 모두 갖춰졌을 때만 signingConfig 연결 (불완전 시 미서명)
oilbeaneda File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| source "https://rubygems.org" | ||
|
|
||
| gem "fastlane" | ||
|
|
||
| # fastlane 플러그인 (fastlane/Pluginfile 에 정의) | ||
| plugins_path = File.join(File.dirname(__FILE__), "fastlane", "Pluginfile") | ||
| eval_gemfile(plugins_path) if File.exist?(plugins_path) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Google Play(supply) 인증용 서비스 계정 JSON — 환경변수 PLAY_SERVICE_ACCOUNT_JSON 로 경로 지정. | ||
| # (Firebase App Distribution 배포에는 불필요) | ||
| json_key_file(ENV["PLAY_SERVICE_ACCOUNT_JSON"]) if ENV["PLAY_SERVICE_ACCOUNT_JSON"] | ||
|
|
||
| package_name("com.moball.app") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| require "json" | ||
|
|
||
| # 문서: https://docs.fastlane.tools | ||
| # 사용법은 fastlane/README.md 참고. | ||
|
|
||
| default_platform(:android) | ||
|
|
||
| # fastlane 실행 시 fastlane/README.md 자동 생성/덮어쓰기 방지 (직접 작성한 가이드 유지). | ||
| skip_docs | ||
|
|
||
| # google-services.json 에서 Firebase App ID를 읽어온다. | ||
| # (google-services.json 은 .gitignore 대상이라 값을 코드에 하드코딩하지 않는다.) | ||
| # CI 등에서 파일이 없으면 FIREBASE_APP_ID 환경변수로 대체 가능. | ||
| def firebase_app_id | ||
| return ENV["FIREBASE_APP_ID"] unless ENV["FIREBASE_APP_ID"].to_s.empty? | ||
|
|
||
| path = File.expand_path("../app/google-services.json", __dir__) | ||
| unless File.exist?(path) | ||
| UI.user_error!("google-services.json 을 찾을 수 없습니다. FIREBASE_APP_ID 환경변수를 설정하거나 파일을 배치하세요: #{path}") | ||
| end | ||
| JSON.parse(File.read(path))["client"][0]["client_info"]["mobilesdk_app_id"] | ||
| end | ||
|
|
||
| platform :android do | ||
| desc "정적 분석(ktlint) + 단위 테스트 실행" | ||
| lane :test do | ||
| gradle(task: "ktlintCheck") | ||
| gradle(task: "testDebugUnitTest") | ||
| end | ||
|
|
||
| desc "테스터에게 배포 (Firebase App Distribution)\n" \ | ||
| "옵션 예) fastlane android beta notes:\"수정 내용\" groups:\"모여볼\"" | ||
| lane :beta do |options| | ||
| # 배포 전 품질 게이트 | ||
| test | ||
|
|
||
| # 빌드 — 현재는 debug 서명 APK로 배포한다(별도 upload keystore 불필요). | ||
| # 업로드 keystore를 붙이면 아래 build_type 을 "Release"로 바꾸면 된다. | ||
| gradle(task: "clean") | ||
| gradle(task: "assemble", build_type: "Debug") | ||
|
|
||
| # Firebase App Distribution 업로드 | ||
| firebase_app_distribution( | ||
| app: firebase_app_id, | ||
| android_artifact_type: "APK", | ||
| android_artifact_path: lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH], | ||
| service_credentials_file: ENV["FIREBASE_SERVICE_ACCOUNT_JSON"], | ||
| groups: options[:groups] || "모여볼", # Firebase App Distribution 그룹 alias (콘솔의 alias와 정확히 일치해야 함) | ||
| release_notes: options[:notes] || last_git_commit[:message], | ||
| ) | ||
|
|
||
| UI.success("Firebase App Distribution 업로드 완료 🚀") | ||
| end | ||
|
|
||
| desc "Google Play 업로드 (supply)\n" \ | ||
| "옵션 예) fastlane android deploy track:internal\n" \ | ||
| "track: internal(기본)/alpha/beta/production, status: completed(기본)/draft" | ||
| lane :deploy do |options| | ||
| # 배포 전 품질 게이트 | ||
| test | ||
|
|
||
| # 릴리즈 서명된 AAB 빌드 (Play 는 APK 가 아니라 AAB) | ||
| # ⚠️ local.properties 에 keystore.* 가 설정돼 있어야 서명된다. | ||
| gradle(task: "clean") | ||
| gradle(task: "bundle", build_type: "Release") | ||
|
|
||
| # Play Console 업로드 (스토어 리스팅/스크린샷은 건드리지 않고 바이너리만 올린다) | ||
| # 인증(json_key)은 fastlane/Appfile 의 PLAY_SERVICE_ACCOUNT_JSON 에서 읽는다. | ||
| upload_to_play_store( | ||
| track: options[:track] || "internal", | ||
| aab: lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH], | ||
| release_status: options[:status] || "completed", | ||
| skip_upload_apk: true, | ||
| skip_upload_metadata: true, | ||
| skip_upload_images: true, | ||
| skip_upload_screenshots: true, | ||
| ) | ||
|
|
||
| UI.success("Google Play (#{options[:track] || "internal"} 트랙) 업로드 완료 🚀") | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # fastlane 플러그인 목록. `bundle install` 시 함께 설치된다. | ||
| gem "fastlane-plugin-firebase_app_distribution" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # Fastlane — 모여볼 릴리즈 자동화 | ||
|
|
||
| 빌드·테스트·배포를 한 줄 명령으로 실행한다. 배포는 **Firebase App Distribution**(테스터 배포)을 사용한다. | ||
|
|
||
| ## Lanes | ||
|
|
||
| | Lane | 설명 | 명령 | | ||
| |------|------|------| | ||
| | `test` | ktlint + 단위 테스트 | `bundle exec fastlane android test` | | ||
| | `beta` | 빌드 후 테스터에게 배포 (Firebase App Distribution) | `bundle exec fastlane android beta` | | ||
| | `deploy` | 릴리즈 AAB 빌드 후 Google Play 업로드 (supply) | `bundle exec fastlane android deploy` | | ||
|
|
||
| `beta` 옵션: | ||
|
|
||
| ```bash | ||
| # 릴리즈 노트 / 대상 그룹 지정 (groups 미지정 시 기본값 "모여볼") | ||
| bundle exec fastlane android beta notes:"공유 기능 추가" groups:"모여볼" | ||
| ``` | ||
|
|
||
| ## 최초 1회 세팅 | ||
|
|
||
| ### 1) fastlane 설치 | ||
|
|
||
| 시스템 gem 폴더 대신 **프로젝트 로컬(`vendor/bundle`)** 에 설치한다(권한 이슈 회피, `.gitignore` 처리됨). | ||
|
|
||
| ```bash | ||
| gem install bundler | ||
| bundle config set --local path 'vendor/bundle' | ||
| bundle install # Gemfile + fastlane/Pluginfile 의 플러그인까지 설치 | ||
| ``` | ||
|
|
||
| > **권한 오류**(`Permission denied @ rb_sysopen ... /opt/homebrew/.../plugins/rdoc_plugin.rb`)가 나면, | ||
| > 위 `bundle config set --local path 'vendor/bundle'` 를 먼저 실행한 뒤 `bundle install` 하면 된다. | ||
| > (Homebrew Ruby 전역 폴더에 쓰려다 막히는 문제 — 로컬 설치로 우회) | ||
|
|
||
| 설치 확인: | ||
|
|
||
| ```bash | ||
| bundle exec fastlane lanes | ||
| ``` | ||
|
|
||
| ### 2) Firebase App Distribution 인증 (서비스 계정) | ||
|
|
||
| CI/자동화에서는 서비스 계정 JSON을 쓴다. | ||
|
|
||
| 1. [Google Cloud Console](https://console.cloud.google.com) → 해당 Firebase 프로젝트 → **서비스 계정** 생성 | ||
| 2. 역할: **Firebase App Distribution Admin** 부여 | ||
| 3. JSON 키 다운로드 → 안전한 경로에 저장 (**절대 git에 커밋 금지**) | ||
| 4. 환경변수로 경로 지정: | ||
|
|
||
| ```bash | ||
| export FIREBASE_SERVICE_ACCOUNT_JSON="/절대/경로/service-account.json" | ||
| ``` | ||
|
|
||
| > 로컬에서 잠깐 테스트만 할 땐 `firebase login` 후 `FIREBASE_SERVICE_ACCOUNT_JSON` 없이 실행해도 된다(대화형 인증). | ||
|
|
||
| ### 3) 테스터 그룹 | ||
|
|
||
| Firebase 콘솔 → App Distribution → **테스터 및 그룹** 에서 그룹을 만들고 테스터 이메일을 추가한다. | ||
|
|
||
| > ⚠️ **`groups:` 값은 그룹의 표시 이름이 아니라 `alias`(별칭)와 정확히 일치해야 한다.** | ||
| > 현재 프로젝트 그룹 alias는 **`모여볼`** 이고, `Fastfile`의 기본값도 이걸로 설정돼 있다. | ||
| > (다른 그룹을 쓰려면 콘솔에서 alias를 확인해 `groups:"<alias>"` 로 지정) | ||
|
|
||
| ## 참고 | ||
|
|
||
| - **Firebase App ID**는 `app/google-services.json` 에서 자동으로 읽는다. 파일이 없는 환경(CI 등)에서는 `FIREBASE_APP_ID` 환경변수로 대체한다. | ||
| - 현재 `beta`는 **debug 서명 APK**를 배포한다(별도 upload keystore 불필요). 릴리즈 서명(Play 앱 서명 + upload keystore)이 준비되면 `Fastfile`의 `build_type`을 `"Release"`로 바꾼다. | ||
| - Google Play 스토어 직접 배포(`supply`)는 upload keystore + Play Console 서비스 계정이 준비되면 `Fastfile`의 주석 처리된 `deploy` 레인을 활성화한다. (#34 후속) | ||
|
|
||
| ## Google Play 배포(`deploy`) 세팅 | ||
|
|
||
| `deploy` 레인은 **릴리즈 서명된 AAB**를 빌드해 Google Play에 업로드한다(`supply`). 다음 2가지가 필요하다. | ||
|
|
||
| ### A) 업로드 keystore (릴리즈 서명) | ||
|
|
||
| > ⚠️ 앱이 이미 Play에 출시돼 있으면 **최초 출시 때 서명한 그 upload keystore를 그대로** 써야 한다. 다른 키로 서명하면 Play가 업데이트를 거부한다. (새 키 생성 ❌) | ||
|
|
||
| `local.properties` 에 keystore 정보를 추가한다 (이 파일은 git에 안 올라감): | ||
|
|
||
| ```properties | ||
| keystore.file=/절대/경로/moball-upload.jks | ||
| keystore.password=**** | ||
| key.alias=**** | ||
| key.password=**** | ||
| ``` | ||
|
|
||
| > 값이 없으면 릴리즈는 **미서명**으로 빌드된다(=Play 업로드 불가). CI에서는 같은 이름의 환경변수(`KEYSTORE_FILE` 등)로 대체 가능. | ||
|
|
||
| ### B) Play Console 서비스 계정 (업로드 권한) | ||
|
|
||
| Firebase 것과 **별개**다. | ||
|
|
||
| 1. [Play Console](https://play.google.com/console) → **설정 → API 액세스** → Google Cloud 프로젝트 연결 | ||
| 2. 서비스 계정 생성 → JSON 키 다운로드 | ||
| 3. Play Console에서 그 서비스 계정에 **앱 접근 권한**(최소 "출시 관리자"/"버전 업로드") 부여 | ||
| 4. 환경변수로 경로 지정: | ||
|
|
||
| ```bash | ||
| export PLAY_SERVICE_ACCOUNT_JSON="/절대/경로/play-service-account.json" | ||
| ``` | ||
|
|
||
| ### 사용 | ||
|
|
||
| ```bash | ||
| # 내부 테스트 트랙(기본)에 업로드 | ||
| bundle exec fastlane android deploy | ||
|
|
||
| # 트랙/상태 지정: track = internal(기본)/alpha/beta/production, status = completed(기본)/draft | ||
| bundle exec fastlane android deploy track:internal | ||
| bundle exec fastlane android deploy track:production status:draft | ||
| ``` | ||
|
|
||
| > ⚠️ **매 업로드마다 `versionCode`를 올려야 한다.** Play는 같은 versionCode 재업로드를 거부한다. | ||
| > 현재는 `gradle/libs.versions.toml` 의 `versionCode` 를 수동으로 증가시킨다. | ||
|
|
||
| ## 문제 해결 | ||
|
|
||
| - **`[!] Invalid request (Google::Apis::ClientError)`** — 업로드는 됐는데 "Distributing release"에서 실패하면, `groups:` 의 **그룹 alias가 콘솔과 불일치**하는 경우다. 콘솔에서 alias를 확인해 정확히 맞춘다. (현재 alias: `모여볼`) | ||
| - **`fastlane requires your locale to be set to UTF-8`** / 한글 릴리즈 노트 깨짐 — 로케일을 UTF-8로 설정한다: | ||
| ```bash | ||
| export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 | ||
| ``` | ||
| - `fastlane/README.md` 는 `Fastfile` 상단의 `skip_docs` 로 자동 덮어쓰기를 막아둔 상태다(직접 작성한 가이드 유지). | ||
|
|
||
| ## CI (GitHub Actions) 연동 예시 | ||
|
|
||
| ```yaml | ||
| - uses: ruby/setup-ruby@v1 | ||
| with: { ruby-version: '3.3', bundler-cache: true } | ||
| - name: Distribute to testers | ||
| env: | ||
| FIREBASE_SERVICE_ACCOUNT_JSON: ${{ github.workspace }}/sa.json | ||
| GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} | ||
| run: | | ||
| echo "$GOOGLE_SERVICES_JSON" > app/google-services.json | ||
| echo '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}' > sa.json | ||
| bundle exec fastlane android beta | ||
| ``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.