build-android-app #9
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
| # =================================================================== | |
| # Flutter Android 테스트용 APK 빌드 워크플로우 | |
| # =================================================================== | |
| # | |
| # 이 워크플로우는 기능 브랜치에서 수동으로 실행하여 | |
| # 테스트용 Android APK를 빌드합니다. | |
| # | |
| # 주요 특징: | |
| # - workflow_dispatch로 수동 실행 또는 repository_dispatch로 트리거 | |
| # - 버전 관리 불필요 (Android는 버전 제약 없음) | |
| # - 브랜치명에서 이슈 번호 자동 추출 (YYYYMMDD_#이슈번호_내용 형식) | |
| # - GitHub API로 이슈 정보 가져와서 빌드 정보에 포함 | |
| # - APK 파일만 생성 (Play Store 배포 제거) | |
| # - 아티팩트로 업로드하여 다운로드 가능 (최대 500MB/아티팩트) | |
| # - 아티팩트 이름에 브랜치명 포함하여 구분 용이 | |
| # | |
| # 사용 방법: | |
| # 1. 기능 브랜치로 체크아웃 | |
| # 2. GitHub Actions에서 이 워크플로우 선택 | |
| # 3. "Run workflow" 버튼 클릭 | |
| # 4. 빌드 완료 후 아티팩트에서 APK 다운로드 | |
| # | |
| # =================================================================== | |
| # 📋 필요한 GitHub Secrets | |
| # =================================================================== | |
| # | |
| # 📝 환경 설정 (선택): | |
| # - ENV_FILE (또는 ENV) : .env 파일 내용 (앱에서 사용하는 환경변수) | |
| # - DEBUG_KEYSTORE : debug.keystore 파일 (base64 인코딩) | |
| # - GOOGLE_SERVICES_JSON : google-services.json 내용 (Firebase 사용 시) | |
| # | |
| # =================================================================== | |
| name: PROJECT-Flutter-Android-Test-APK | |
| on: | |
| workflow_dispatch: | |
| repository_dispatch: | |
| types: [build-android-app] | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| # ============================================ | |
| # 🔧 프로젝트별 설정 (아래 값들을 수정하세요) | |
| # ============================================ | |
| env: | |
| FLUTTER_VERSION: "3.35.5" | |
| JAVA_VERSION: "17" | |
| ENV_FILE_PATH: ".env" | |
| jobs: | |
| prepare-test-build: | |
| name: 테스트 빌드 준비 | |
| runs-on: ubuntu-latest | |
| outputs: | |
| issue_url: ${{ steps.issue_info.outputs.issue_url }} | |
| issue_title: ${{ steps.issue_info.outputs.issue_title }} | |
| issue_number: ${{ steps.issue_info.outputs.issue_number }} | |
| branch_name: ${{ steps.issue_info.outputs.branch_name }} | |
| branch_name_safe: ${{ steps.issue_info.outputs.branch_name_safe }} | |
| commit_hash: ${{ steps.build_info.outputs.commit_hash }} | |
| pr_number: ${{ steps.build_info.outputs.pr_number }} | |
| build_number: ${{ steps.build_info.outputs.build_number }} | |
| progress_comment_id: ${{ steps.progress.outputs.comment_id }} | |
| prepare_start: ${{ steps.progress.outputs.start_time }} | |
| steps: | |
| - name: 브랜치 존재 여부 사전 확인 | |
| id: verify_branch | |
| if: github.event_name == 'repository_dispatch' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const branchName = '${{ github.event.client_payload.branch_name }}'; | |
| const prNumber = '${{ github.event.client_payload.pr_number }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| console.log(`🔍 브랜치 존재 여부 확인: ${branchName}`); | |
| try { | |
| await github.rest.repos.getBranch({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| branch: branchName | |
| }); | |
| console.log(`✅ 브랜치 존재 확인됨: ${branchName}`); | |
| } catch (error) { | |
| if (error.status === 404) { | |
| console.log(`❌ 브랜치를 찾을 수 없음: ${branchName}`); | |
| // 에러 댓글 작성 | |
| const body = [ | |
| '🤖 ❌ **Android 테스트 APK 빌드 실패 - 브랜치를 찾을 수 없습니다**', | |
| '', | |
| '| 항목 | 값 |', | |
| '|------|-----|', | |
| `| **요청된 브랜치** | \`${branchName}\` |`, | |
| `| **이슈/PR** | #${prNumber} |`, | |
| '', | |
| '### 💡 확인 사항', | |
| '1. 브랜치가 원격 저장소에 push되었는지 확인하세요', | |
| '2. "Guide by SUH-LAB" 댓글의 브랜치명이 올바른지 확인하세요', | |
| '3. 브랜치명에 오타가 없는지 확인하세요', | |
| '', | |
| `🔗 [워크플로우 로그](${runUrl})` | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: parseInt(prNumber), | |
| body: body | |
| }); | |
| core.setFailed(`브랜치를 찾을 수 없습니다: ${branchName}`); | |
| } else { | |
| core.setFailed(`브랜치 확인 중 오류: ${error.message}`); | |
| } | |
| } | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| # 진행 상황 댓글 시스템 | |
| # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |
| - name: 진행 상황 댓글 생성 | |
| id: progress | |
| if: github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const prNumber = parseInt('${{ github.event.client_payload.pr_number }}'); | |
| const branchName = '${{ github.event.client_payload.branch_name }}'; | |
| const buildNumber = '${{ github.event.client_payload.build_number }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| const startTime = Date.now(); | |
| const body = [ | |
| '## 🤖 Android 테스트 APK 빌드 진행 중...', | |
| '', | |
| '| 단계 | 상태 | 소요 시간 |', | |
| '|------|------|----------|', | |
| '| 🔧 준비 | ⏳ 진행 중... | - |', | |
| '| 🔨 APK 빌드 | ⏸️ 대기 | - |', | |
| '', | |
| '| 항목 | 값 |', | |
| '|------|-----|', | |
| `| **앱 버전** | \`0.0.0(${buildNumber})\` |`, | |
| `| **브랜치** | \`${branchName}\` |`, | |
| '', | |
| `**[📋 실시간 로그 보기](${runUrl})**`, | |
| '', | |
| '---', | |
| '*🤖 이 댓글은 자동으로 업데이트됩니다.*' | |
| ].join('\n'); | |
| const { data: comment } = await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: body | |
| }); | |
| console.log(`✅ 진행 상황 댓글 생성 완료: #${comment.id}`); | |
| core.setOutput('comment_id', comment.id); | |
| core.setOutput('start_time', startTime); | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref }} | |
| - name: Pull latest changes | |
| run: | | |
| BRANCH_NAME="${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}" | |
| echo "🌿 브랜치: $BRANCH_NAME" | |
| git pull origin "$BRANCH_NAME" || echo "⚠️ Pull 실패 (이미 최신 상태일 수 있음)" | |
| # 브랜치명에서 이슈 번호 추출 및 GitHub API로 이슈 정보 가져오기 | |
| - name: 브랜치명에서 이슈 정보 추출 | |
| id: issue_info | |
| run: | | |
| # repository_dispatch인 경우 client_payload에서 브랜치명 가져오기 | |
| if [ "${{ github.event_name }}" == "repository_dispatch" ]; then | |
| BRANCH_NAME="${{ github.event.client_payload.branch_name }}" | |
| ISSUE_NUMBER="${{ github.event.client_payload.issue_number }}" | |
| else | |
| BRANCH_NAME="${{ github.ref_name }}" | |
| # 브랜치명에서 이슈 번호 추출 (#387 형식) | |
| ISSUE_NUMBER=$(echo "$BRANCH_NAME" | sed -n 's/.*#\([0-9]*\).*/\1/p') | |
| fi | |
| echo "🌿 브랜치명: $BRANCH_NAME" | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| # 브랜치명을 아티팩트 이름에 사용 가능한 형식으로 변환 | |
| # 특수문자를 밑줄로 대체 | |
| BRANCH_NAME_SAFE=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9_-]/_/g' | cut -c1-50) | |
| echo "branch_name_safe=$BRANCH_NAME_SAFE" >> $GITHUB_OUTPUT | |
| if [ -z "$ISSUE_NUMBER" ]; then | |
| echo "⚠️ 브랜치명에서 이슈 번호를 찾을 수 없습니다." | |
| echo "issue_url=" >> $GITHUB_OUTPUT | |
| echo "issue_title=" >> $GITHUB_OUTPUT | |
| echo "issue_number=" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ 추출된 이슈 번호: #$ISSUE_NUMBER" | |
| ISSUE_URL="https://github.com/${{ github.repository }}/issues/$ISSUE_NUMBER" | |
| echo "issue_url=$ISSUE_URL" >> $GITHUB_OUTPUT | |
| echo "issue_number=$ISSUE_NUMBER" >> $GITHUB_OUTPUT | |
| # GitHub API로 이슈 정보 가져오기 | |
| echo "🔍 GitHub API로 이슈 정보 조회 중..." | |
| ISSUE_RESPONSE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
| "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER") | |
| ISSUE_TITLE=$(echo "$ISSUE_RESPONSE" | jq -r '.title // "이슈 정보 없음"') | |
| ISSUE_STATE=$(echo "$ISSUE_RESPONSE" | jq -r '.state // "unknown"') | |
| if [ "$ISSUE_TITLE" = "null" ] || [ "$ISSUE_TITLE" = "이슈 정보 없음" ]; then | |
| echo "⚠️ 이슈 #$ISSUE_NUMBER를 찾을 수 없습니다." | |
| echo "issue_title=" >> $GITHUB_OUTPUT | |
| else | |
| echo "📋 이슈 제목: $ISSUE_TITLE" | |
| echo "📌 이슈 상태: $ISSUE_STATE" | |
| echo "issue_title=$ISSUE_TITLE" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| # 빌드 정보 생성 | |
| - name: 빌드 정보 생성 | |
| id: build_info | |
| run: | | |
| COMMIT_SHA="${{ github.sha }}" | |
| COMMIT_SHORT=$(echo "$COMMIT_SHA" | cut -c1-7) | |
| BUILD_DATE=$(date '+%Y-%m-%d %H:%M:%S') | |
| # repository_dispatch 이벤트인 경우 PR 번호를 빌드 번호로 사용 | |
| if [ "${{ github.event_name }}" == "repository_dispatch" ]; then | |
| BUILD_NUMBER="${{ github.event.client_payload.build_number }}" | |
| PR_NUMBER="${{ github.event.client_payload.pr_number }}" | |
| echo "📋 repository_dispatch 이벤트 감지" | |
| echo " PR 번호: #$PR_NUMBER" | |
| echo " 빌드 번호: $BUILD_NUMBER (PR 번호 사용)" | |
| else | |
| BUILD_NUMBER="${{ github.run_number }}" | |
| PR_NUMBER="" | |
| echo "📋 workflow_dispatch 이벤트 감지" | |
| echo " 빌드 번호: $BUILD_NUMBER (기본값)" | |
| fi | |
| echo "commit_hash=$COMMIT_SHORT" >> $GITHUB_OUTPUT | |
| echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "build_date=$BUILD_DATE" >> $GITHUB_OUTPUT | |
| echo "📋 빌드 정보:" | |
| echo " 빌드 번호: #$BUILD_NUMBER" | |
| if [ -n "$PR_NUMBER" ]; then | |
| echo " PR 번호: #$PR_NUMBER" | |
| fi | |
| echo " 커밋 해시: $COMMIT_SHORT" | |
| echo " 빌드 날짜: $BUILD_DATE" | |
| # 진행 상황 업데이트 - 준비 완료 | |
| - name: 진행 상황 업데이트 - 준비 완료 | |
| if: github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number != '' && steps.progress.outputs.comment_id != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const commentId = parseInt('${{ steps.progress.outputs.comment_id }}'); | |
| const startTime = parseInt('${{ steps.progress.outputs.start_time }}'); | |
| const branchName = '${{ github.event.client_payload.branch_name }}'; | |
| const buildNumber = '${{ github.event.client_payload.build_number }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| const now = Date.now(); | |
| const elapsed = now - startTime; | |
| const minutes = Math.floor(elapsed / 60000); | |
| const seconds = Math.floor((elapsed % 60000) / 1000); | |
| const duration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`; | |
| const body = [ | |
| '## 🤖 Android 테스트 APK 빌드 진행 중...', | |
| '', | |
| '| 단계 | 상태 | 소요 시간 |', | |
| '|------|------|----------|', | |
| `| 🔧 준비 | ✅ 완료 | ${duration} |`, | |
| '| 🔨 APK 빌드 | ⏳ 진행 중... | - |', | |
| '', | |
| '| 항목 | 값 |', | |
| '|------|-----|', | |
| `| **앱 버전** | \`0.0.0(${buildNumber})\` |`, | |
| `| **브랜치** | \`${branchName}\` |`, | |
| '', | |
| `**[📋 실시간 로그 보기](${runUrl})**`, | |
| '', | |
| '---', | |
| '*🤖 이 댓글은 자동으로 업데이트됩니다.*' | |
| ].join('\n'); | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: commentId, | |
| body: body | |
| }); | |
| console.log(`✅ 진행 상황 업데이트 완료: 준비 완료 (${duration})`); | |
| build-android-test: | |
| name: Android 테스트 APK 빌드 | |
| runs-on: ubuntu-latest | |
| needs: prepare-test-build | |
| steps: | |
| # 빌드 시작 시간 기록 | |
| - name: 빌드 시작 시간 기록 | |
| id: build_start | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| core.setOutput('time', Date.now().toString()); | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref }} | |
| - name: Pull latest changes | |
| run: git pull origin ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }} | |
| # .env 파일 생성 | |
| - name: Create .env file | |
| run: | | |
| echo "${{ secrets.ENV_FILE || secrets.ENV }}" > ${{ env.ENV_FILE_PATH }} | |
| echo "✅ ${{ env.ENV_FILE_PATH }} file created" | |
| # Release Keystore 설정 (Play Store 워크플로우와 동일한 방식) | |
| - name: Setup Release Keystore | |
| run: | | |
| mkdir -p android/app/keystore | |
| echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore/key.jks | |
| echo "✅ Release Keystore 생성 완료" | |
| ls -la android/app/keystore/ | |
| # key.properties 생성 (Release 서명 정보) | |
| - name: Create key.properties | |
| run: | | |
| cat > android/key.properties << EOF | |
| storeFile=app/keystore/key.jks | |
| storePassword=${{ secrets.RELEASE_KEYSTORE_PASSWORD }} | |
| keyAlias=${{ secrets.RELEASE_KEY_ALIAS }} | |
| keyPassword=${{ secrets.RELEASE_KEY_PASSWORD }} | |
| EOF | |
| echo "✅ key.properties 생성 완료" | |
| # google-services.json 생성 (선택적) | |
| - name: Create google-services.json | |
| shell: bash | |
| run: | | |
| if [ -n "${{ secrets.GOOGLE_SERVICES_JSON }}" ]; then | |
| mkdir -p android/app | |
| printf '%s' "${GOOGLE_SERVICES_JSON}" > android/app/google-services.json | |
| echo "✅ google-services.json created" | |
| else | |
| echo "ℹ️ GOOGLE_SERVICES_JSON secret not provided, skipping" | |
| fi | |
| env: | |
| GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} | |
| # Flutter 설정 | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: ${{ env.FLUTTER_VERSION }} | |
| cache: true | |
| - name: Verify Flutter version | |
| run: | | |
| echo "✅ Flutter setup completed" | |
| flutter --version | |
| # Flutter 및 Gradle 캐시 | |
| - name: Cache Flutter dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.pub-cache | |
| key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }} | |
| restore-keys: ${{ runner.os }}-flutter-pub- | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| ~/.gradle/buildOutputCleanup | |
| key: ${{ runner.os }}-gradle-8.12-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/gradle.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle-8.12- | |
| ${{ runner.os }}-gradle- | |
| # 프로젝트 의존성 설치 | |
| - name: Install dependencies | |
| run: | | |
| flutter pub get | |
| echo "✅ Dependencies installed" | |
| # Gradle 셋업 | |
| - name: Setup Gradle | |
| working-directory: android | |
| run: | | |
| chmod +x gradlew | |
| echo "✅ Gradle wrapper permissions set" | |
| # Java 설정 | |
| - name: Set up Java | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: "temurin" | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Verify Java version | |
| run: | | |
| echo "✅ Java setup completed" | |
| java -version | |
| # Ruby 설정 | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "3.4.1" | |
| bundler-cache: true | |
| - name: Verify Ruby version | |
| run: | | |
| echo "✅ Ruby setup completed" | |
| ruby -v | |
| # Fastlane 설치 | |
| - name: Install Fastlane | |
| run: | | |
| gem install fastlane | |
| echo "✅ Fastlane installed" | |
| fastlane --version | |
| # APK 파일명 생성 | |
| - name: Generate APK filename | |
| id: apk_filename | |
| run: | | |
| COMMIT_SHORT="${{ needs.prepare-test-build.outputs.commit_hash }}" | |
| BUILD_NUMBER="${{ needs.prepare-test-build.outputs.build_number }}" | |
| APK_NAME="flutter-test-${BUILD_NUMBER}-${COMMIT_SHORT}.apk" | |
| echo "apk_name=$APK_NAME" >> $GITHUB_OUTPUT | |
| echo "📦 APK 파일명: $APK_NAME" | |
| # APK 빌드 (Fastlane 또는 직접 빌드) | |
| - name: Build APK | |
| run: | | |
| # Fastlane Fastfile이 있으면 사용, 없으면 직접 빌드 | |
| if [ -f "android/fastlane/Fastfile" ]; then | |
| echo "📦 Fastlane을 사용하여 빌드..." | |
| cd android | |
| fastlane build --verbose || flutter build apk --release | |
| else | |
| echo "📦 Flutter 직접 빌드..." | |
| flutter build apk --release | |
| fi | |
| ls -la ./build/app/outputs/flutter-apk/ || true | |
| echo "✅ APK built" | |
| # APK 파일 이름 변경 및 준비 | |
| - name: Rename and Prepare APK | |
| run: | | |
| mkdir -p ./android/app/build/outputs/apk/release/ | |
| APK_NAME="${{ steps.apk_filename.outputs.apk_name }}" | |
| mv ./build/app/outputs/flutter-apk/app-release.apk ./android/app/build/outputs/apk/release/${APK_NAME} | |
| echo "✅ APK renamed to ${APK_NAME}" | |
| ls -la ./android/app/build/outputs/apk/release/ | |
| # APK 파일 크기 확인 | |
| APK_SIZE=$(stat -c%s "./android/app/build/outputs/apk/release/${APK_NAME}" 2>/dev/null || stat -f%z "./android/app/build/outputs/apk/release/${APK_NAME}" 2>/dev/null || echo "0") | |
| APK_SIZE_MB=$(echo "scale=2; $APK_SIZE / 1024 / 1024" | bc) | |
| echo "📦 APK 파일 크기: ${APK_SIZE_MB}MB" | |
| # 아티팩트 용량 제한 확인 (500MB) | |
| if [ "$APK_SIZE" -gt 524288000 ]; then | |
| echo "⚠️ 경고: APK 파일 크기가 500MB를 초과합니다 (${APK_SIZE_MB}MB)" | |
| else | |
| echo "✅ APK 파일 크기가 아티팩트 제한 내입니다 (${APK_SIZE_MB}MB / 500MB)" | |
| fi | |
| # 빌드 정보 파일 생성 | |
| - name: Create build info file | |
| run: | | |
| BUILD_NUMBER="${{ needs.prepare-test-build.outputs.build_number }}" | |
| PR_NUMBER="${{ needs.prepare-test-build.outputs.pr_number }}" | |
| BRANCH_NAME="${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}" | |
| COMMIT_SHA="${{ github.sha }}" | |
| COMMIT_SHORT="${{ needs.prepare-test-build.outputs.commit_hash }}" | |
| BUILD_DATE=$(date '+%Y-%m-%d %H:%M:%S') | |
| cat > build-info.txt << EOF | |
| 테스트 APK 빌드 정보 | |
| 빌드 번호: #$BUILD_NUMBER | |
| EOF | |
| if [ -n "$PR_NUMBER" ]; then | |
| cat >> build-info.txt << EOF | |
| PR 번호: #$PR_NUMBER | |
| EOF | |
| fi | |
| cat >> build-info.txt << EOF | |
| 브랜치: $BRANCH_NAME | |
| 커밋: $COMMIT_SHORT | |
| 전체 커밋 해시: $COMMIT_SHA | |
| 빌드 날짜: $BUILD_DATE | |
| EOF | |
| # 이슈 정보가 있으면 추가 | |
| if [ -n "${{ needs.prepare-test-build.outputs.issue_number }}" ]; then | |
| ISSUE_NUMBER="${{ needs.prepare-test-build.outputs.issue_number }}" | |
| ISSUE_TITLE="${{ needs.prepare-test-build.outputs.issue_title }}" | |
| ISSUE_URL="${{ needs.prepare-test-build.outputs.issue_url }}" | |
| cat >> build-info.txt << EOF | |
| 관련 이슈: | |
| - #$ISSUE_NUMBER: $ISSUE_TITLE | |
| - URL: $ISSUE_URL | |
| EOF | |
| fi | |
| echo "📋 빌드 정보 파일 생성 완료:" | |
| cat build-info.txt | |
| # 빌드 메타데이터 파일 생성 (댓글 작성 워크플로우용) | |
| - name: Create build metadata file | |
| run: | | |
| cat > build-metadata.json << EOF | |
| { | |
| "pr_number": "${{ needs.prepare-test-build.outputs.pr_number }}", | |
| "build_number": "${{ needs.prepare-test-build.outputs.build_number }}", | |
| "issue_number": "${{ needs.prepare-test-build.outputs.issue_number }}", | |
| "branch_name": "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}" | |
| } | |
| EOF | |
| cat build-metadata.json | |
| # APK와 빌드 정보를 아티팩트로 업로드 (브랜치명 포함) | |
| - name: Upload APK and build info as Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-apk-${{ needs.prepare-test-build.outputs.branch_name_safe }}-${{ needs.prepare-test-build.outputs.build_number }} | |
| path: | | |
| ./android/app/build/outputs/apk/release/*.apk | |
| build-info.txt | |
| build-metadata.json | |
| retention-days: 7 | |
| if-no-files-found: error | |
| # 성공 알림 | |
| - name: Notify Build Success | |
| if: success() | |
| run: | | |
| echo "✅ Android 테스트 APK 빌드 성공!" | |
| echo "빌드 번호: ${{ needs.prepare-test-build.outputs.build_number }}" | |
| if [ -n "${{ needs.prepare-test-build.outputs.pr_number }}" ]; then | |
| echo "PR 번호: ${{ needs.prepare-test-build.outputs.pr_number }}" | |
| fi | |
| echo "브랜치: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch_name || github.ref_name }}" | |
| echo "커밋: ${{ needs.prepare-test-build.outputs.commit_hash }}" | |
| if [ -n "${{ needs.prepare-test-build.outputs.issue_url }}" ]; then | |
| echo "관련 이슈: ${{ needs.prepare-test-build.outputs.issue_url }}" | |
| fi | |
| echo "" | |
| echo "📦 아티팩트에서 APK 파일을 다운로드할 수 있습니다." | |
| - name: Notify on Failure | |
| if: failure() | |
| run: | | |
| echo "❌ Android 테스트 APK 빌드 실패!" | |
| echo "로그를 확인해주세요." | |
| # 진행 상황 최종 업데이트 - 성공 | |
| - name: 진행 상황 최종 업데이트 - 성공 | |
| if: success() && github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number != '' && needs.prepare-test-build.outputs.progress_comment_id != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const commentId = parseInt('${{ needs.prepare-test-build.outputs.progress_comment_id }}'); | |
| const prepareStart = parseInt('${{ needs.prepare-test-build.outputs.prepare_start }}'); | |
| const buildStart = parseInt('${{ steps.build_start.outputs.time }}'); | |
| const branchName = '${{ github.event.client_payload.branch_name }}'; | |
| const buildNumber = '${{ github.event.client_payload.build_number }}'; | |
| const commitHash = '${{ needs.prepare-test-build.outputs.commit_hash }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| const artifactUrl = `${runUrl}#artifacts`; | |
| const now = Date.now(); | |
| // 준비 단계 소요 시간 | |
| const prepareElapsed = buildStart - prepareStart; | |
| const prepareMin = Math.floor(prepareElapsed / 60000); | |
| const prepareSec = Math.floor((prepareElapsed % 60000) / 1000); | |
| const prepareDuration = prepareMin > 0 ? `${prepareMin}분 ${prepareSec}초` : `${prepareSec}초`; | |
| // 빌드 단계 소요 시간 | |
| const buildElapsed = now - buildStart; | |
| const buildMin = Math.floor(buildElapsed / 60000); | |
| const buildSec = Math.floor((buildElapsed % 60000) / 1000); | |
| const buildDuration = buildMin > 0 ? `${buildMin}분 ${buildSec}초` : `${buildSec}초`; | |
| // 전체 소요 시간 | |
| const totalElapsed = now - prepareStart; | |
| const totalMin = Math.floor(totalElapsed / 60000); | |
| const totalSec = Math.floor((totalElapsed % 60000) / 1000); | |
| const totalDuration = totalMin > 0 ? `${totalMin}분 ${totalSec}초` : `${totalSec}초`; | |
| const body = [ | |
| '## 🤖 ✅ Android 테스트 APK 빌드 완료', | |
| '', | |
| '| 단계 | 상태 | 소요 시간 |', | |
| '|------|------|----------|', | |
| `| 🔧 준비 | ✅ 완료 | ${prepareDuration} |`, | |
| `| 🔨 APK 빌드 | ✅ 완료 | ${buildDuration} |`, | |
| '', | |
| `**총 소요 시간: ${totalDuration}**`, | |
| '', | |
| '| 항목 | 값 |', | |
| '|------|-----|', | |
| `| **앱 버전** | \`0.0.0(${buildNumber})\` |`, | |
| `| **브랜치** | \`${branchName}\` |`, | |
| `| **커밋** | \`${commitHash}\` |`, | |
| '', | |
| `📦 **[아티팩트 다운로드](${artifactUrl})**`, | |
| '', | |
| `🔗 [워크플로우 실행 로그](${runUrl})` | |
| ].join('\n'); | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: commentId, | |
| body: body | |
| }); | |
| console.log(`✅ 진행 상황 최종 업데이트 완료: 전체 성공 (${totalDuration})`); | |
| # 진행 상황 최종 업데이트 - 실패 | |
| - name: 진행 상황 최종 업데이트 - 실패 | |
| if: failure() && github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number != '' && needs.prepare-test-build.outputs.progress_comment_id != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const commentId = parseInt('${{ needs.prepare-test-build.outputs.progress_comment_id }}'); | |
| const prepareStart = parseInt('${{ needs.prepare-test-build.outputs.prepare_start }}'); | |
| const buildStart = parseInt('${{ steps.build_start.outputs.time }}'); | |
| const branchName = '${{ github.event.client_payload.branch_name }}'; | |
| const buildNumber = '${{ github.event.client_payload.build_number }}'; | |
| const commitHash = '${{ needs.prepare-test-build.outputs.commit_hash }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| const now = Date.now(); | |
| // 준비 단계 소요 시간 | |
| const prepareElapsed = buildStart - prepareStart; | |
| const prepareMin = Math.floor(prepareElapsed / 60000); | |
| const prepareSec = Math.floor((prepareElapsed % 60000) / 1000); | |
| const prepareDuration = prepareMin > 0 ? `${prepareMin}분 ${prepareSec}초` : `${prepareSec}초`; | |
| // 빌드 단계 소요 시간 | |
| const buildElapsed = now - buildStart; | |
| const buildMin = Math.floor(buildElapsed / 60000); | |
| const buildSec = Math.floor((buildElapsed % 60000) / 1000); | |
| const buildDuration = buildMin > 0 ? `${buildMin}분 ${buildSec}초` : `${buildSec}초`; | |
| const body = [ | |
| '## 🤖 ❌ Android 테스트 APK 빌드 실패', | |
| '', | |
| '| 단계 | 상태 | 소요 시간 |', | |
| '|------|------|----------|', | |
| `| 🔧 준비 | ✅ 완료 | ${prepareDuration} |`, | |
| `| 🔨 APK 빌드 | ❌ 실패 | ${buildDuration} |`, | |
| '', | |
| '| 항목 | 값 |', | |
| '|------|-----|', | |
| `| **앱 버전** | \`0.0.0(${buildNumber})\` |`, | |
| `| **브랜치** | \`${branchName}\` |`, | |
| `| **커밋** | \`${commitHash}\` |`, | |
| '', | |
| '❌ **APK 빌드 중 오류가 발생했습니다.**', | |
| '', | |
| `🔗 [워크플로우 실행 로그 확인](${runUrl})` | |
| ].join('\n'); | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: commentId, | |
| body: body | |
| }); | |
| console.log(`❌ 진행 상황 최종 업데이트 완료: APK 빌드 실패`); |