Nightly #96
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
| name: Nightly | |
| on: | |
| schedule: | |
| - cron: '0 17 * * *' # λ§€μΌ UTC 17:00 (KST 02:00) | |
| workflow_dispatch: | |
| concurrency: | |
| group: nightly | |
| cancel-in-progress: true | |
| jobs: | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # JOB 1 : μ μ λΆμ (Release variant κΈ°μ€) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| static-analysis: | |
| name: Static Analysis (Release) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v6 | |
| with: | |
| cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Generate local.properties | |
| run: | | |
| write_local_property() { | |
| local name="$1" | |
| local value="$2" | |
| if [ -z "$value" ]; then | |
| echo "Missing required local.properties secret: $name" | |
| exit 1 | |
| fi | |
| if [[ "$value" == ${name}* ]]; then | |
| value="${value#*=}" | |
| value="${value# }" | |
| fi | |
| if [[ "$value" == \"*\" ]]; then | |
| value="${value#\"}" | |
| value="${value%\"}" | |
| fi | |
| printf '%s = "%s"\n' "$name" "$value" >> ./local.properties | |
| } | |
| : > ./local.properties | |
| write_local_property "PROD_BASE_URL" "$PROD_BASE_URL" | |
| write_local_property "DEV_BASE_URL" "$DEV_BASE_URL" | |
| write_local_property "TERMS_URL" "$TERMS_URL" | |
| write_local_property "REPORT_FORM_URL" "$REPORT_FORM_URL" | |
| env: | |
| PROD_BASE_URL: ${{ secrets.PROD_BASE_URL }} | |
| DEV_BASE_URL: ${{ secrets.DEV_BASE_URL }} | |
| TERMS_URL: ${{ secrets.TERMS_URL }} | |
| REPORT_FORM_URL: ${{ secrets.REPORT_FORM_URL }} | |
| - name: Create google-services.json | |
| run: | | |
| mkdir -p ${{ github.workspace }}/app | |
| SECRET='${{ secrets.GOOGLE_SERVICES_JSON }}' | |
| TARGET='${{ github.workspace }}/app/google-services.json' | |
| if ! printf '%s' "$SECRET" | jq -e . > /dev/null 2>&1; then | |
| echo "Invalid GOOGLE_SERVICES_JSON secret: must be raw JSON content from app/google-services.json" | |
| exit 1 | |
| fi | |
| printf '%s' "$SECRET" > "$TARGET" | |
| jq -e . "$TARGET" > /dev/null | |
| - name: Run static analysis | |
| run: ./gradlew ktlintCheck detekt lintRelease --continue | |
| - name: Merge detekt SARIF reports | |
| if: always() | |
| run: | | |
| python3 - <<'EOF' | |
| import json, glob | |
| results, rules, tool = [], {}, None | |
| for path in sorted(glob.glob('./**/reports/detekt/detekt.sarif', recursive=True)): | |
| with open(path) as f: | |
| data = json.load(f) | |
| for run in data.get('runs', []): | |
| if tool is None: | |
| tool = run.get('tool', {}) | |
| for rule in run.get('tool', {}).get('driver', {}).get('rules', []): | |
| rules[rule['id']] = rule | |
| results.extend(run.get('results', [])) | |
| if tool is None: | |
| tool = {"driver": {"name": "detekt", "rules": []}} | |
| tool.setdefault('driver', {})['rules'] = list(rules.values()) | |
| merged = { | |
| "version": "2.1.0", | |
| "$schema": "https://json.schemastore.org/sarif-2.1.0.json", | |
| "runs": [{"tool": tool, "results": results}] | |
| } | |
| import os; os.makedirs('build/detekt-sarif', exist_ok=True) | |
| with open('build/detekt-sarif/merged.sarif', 'w') as f: | |
| json.dump(merged, f) | |
| print(f"Merged {len(results)} result(s) from {len(glob.glob('./**/reports/detekt/detekt.sarif', recursive=True))} module(s)") | |
| EOF | |
| - name: Upload detekt SARIF to GitHub Security | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@v4 | |
| with: | |
| sarif_file: build/detekt-sarif/merged.sarif | |
| category: detekt-nightly | |
| - name: Collect lint reports | |
| if: always() | |
| run: | | |
| mkdir -p build/lint-reports | |
| find . -name 'lint-results-*.html' \ | |
| -exec cp {} build/lint-reports/ \; | |
| find . -name 'lint-results-*.sarif' \ | |
| -exec cp {} build/lint-reports/ \; | |
| - name: Upload lint HTML report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: lint-html-report-release | |
| path: build/lint-reports/*.html | |
| retention-days: 30 | |
| - name: Upload lint SARIF to GitHub Security | |
| if: always() && hashFiles('build/lint-reports/*.sarif') != '' | |
| uses: github/codeql-action/upload-sarif@v4 | |
| with: | |
| sarif_file: build/lint-reports | |
| category: lint-nightly | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # JOB 2 : Release λΉλ + Compose Metrics | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| release-build: | |
| name: Release Build & Compose Metrics | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v6 | |
| with: | |
| cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Generate local.properties | |
| run: | | |
| write_local_property() { | |
| local name="$1" | |
| local value="$2" | |
| if [ -z "$value" ]; then | |
| echo "Missing required local.properties secret: $name" | |
| exit 1 | |
| fi | |
| if [[ "$value" == ${name}* ]]; then | |
| value="${value#*=}" | |
| value="${value# }" | |
| fi | |
| if [[ "$value" == \"*\" ]]; then | |
| value="${value#\"}" | |
| value="${value%\"}" | |
| fi | |
| printf '%s = "%s"\n' "$name" "$value" >> ./local.properties | |
| } | |
| : > ./local.properties | |
| write_local_property "PROD_BASE_URL" "$PROD_BASE_URL" | |
| write_local_property "DEV_BASE_URL" "$DEV_BASE_URL" | |
| write_local_property "TERMS_URL" "$TERMS_URL" | |
| write_local_property "REPORT_FORM_URL" "$REPORT_FORM_URL" | |
| env: | |
| PROD_BASE_URL: ${{ secrets.PROD_BASE_URL }} | |
| DEV_BASE_URL: ${{ secrets.DEV_BASE_URL }} | |
| TERMS_URL: ${{ secrets.TERMS_URL }} | |
| REPORT_FORM_URL: ${{ secrets.REPORT_FORM_URL }} | |
| - name: Create google-services.json | |
| run: | | |
| mkdir -p ${{ github.workspace }}/app | |
| SECRET='${{ secrets.GOOGLE_SERVICES_JSON }}' | |
| TARGET='${{ github.workspace }}/app/google-services.json' | |
| if ! printf '%s' "$SECRET" | jq -e . > /dev/null 2>&1; then | |
| echo "Invalid GOOGLE_SERVICES_JSON secret: must be raw JSON content from app/google-services.json" | |
| exit 1 | |
| fi | |
| printf '%s' "$SECRET" > "$TARGET" | |
| jq -e . "$TARGET" > /dev/null | |
| - name: Decode and restore keystore | |
| run: | | |
| KEYSTORE_PATH="${RUNNER_TEMP}/dmsStore.jks" | |
| echo "${{ secrets.KEYSTORE_BASE64 }}" | tr -d ' \n\r' | base64 --decode > "$KEYSTORE_PATH" | |
| echo "KEYSTORE_PATH=$KEYSTORE_PATH" >> $GITHUB_ENV | |
| # ββ assembleRelease + Compose Metrics βββββββββββ | |
| - name: Build Release APK with Compose metrics | |
| env: | |
| KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} | |
| run: ./gradlew assembleRelease -PenableMultiModuleComposeReports=true | |
| - name: Upload Release APK | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-apk-${{ github.run_number }} | |
| path: app/build/outputs/apk/release/*.apk | |
| retention-days: 30 | |
| - name: Cache mendable.jar | |
| id: mendable-cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: tools/mendable.jar | |
| key: mendable-jar-v0.7.0 | |
| - name: Download mendable.jar | |
| if: steps.mendable-cache.outputs.cache-hit != 'true' | |
| run: | | |
| mkdir -p tools | |
| curl -fL --retry 3 --retry-delay 2 \ | |
| -o tools/mendable.jar \ | |
| "https://github.com/jayasuryat/mendable/releases/download/v0.7.0/mendable.jar" | |
| test -s tools/mendable.jar | |
| - name: Generate mendable HTML report | |
| run: | | |
| mkdir -p build/mendable-output | |
| java -jar tools/mendable.jar \ | |
| -i build/compose_metrics \ | |
| -sr \ | |
| -o build/mendable-output \ | |
| -oName mendable-report \ | |
| -eType HTML \ | |
| -rType ALL | |
| - name: Generate mendable JSON report (warnings only) | |
| run: | | |
| java -jar tools/mendable.jar \ | |
| -i build/compose_metrics \ | |
| -sr \ | |
| -o build/mendable-output \ | |
| -oName mendable-warnings \ | |
| -eType JSON \ | |
| -rType WITH_WARNINGS | |
| - name: Check Compose metrics (skippable ratio) | |
| run: | | |
| python3 scripts/check_compose_metrics.py \ | |
| build/mendable-output/mendable-warnings.json \ | |
| --threshold 100 | |
| - name: Upload mendable HTML report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: compose-metrics-report-${{ github.run_number }} | |
| path: build/mendable-output/mendable-report.html | |
| retention-days: 30 | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # JOB 3 : κ²°κ³Ό μλ¦Ό (νμ μ€ν) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββ | |
| notify: | |
| name: Notify Result | |
| runs-on: ubuntu-latest | |
| needs: [ static-analysis, release-build ] | |
| if: always() | |
| steps: | |
| - name: Determine result | |
| id: result | |
| run: | | |
| STATIC="${{ needs.static-analysis.result }}" | |
| BUILD="${{ needs.release-build.result }}" | |
| if [[ "$STATIC" == "success" && "$BUILD" == "success" ]]; then | |
| echo "status=success" >> $GITHUB_OUTPUT | |
| echo "emoji=β " >> $GITHUB_OUTPUT | |
| echo "label=Nightly λΉλ μ±κ³΅" >> $GITHUB_OUTPUT | |
| echo "color=good" >> $GITHUB_OUTPUT | |
| echo "color_int=3066993" >> $GITHUB_OUTPUT | |
| else | |
| echo "status=failure" >> $GITHUB_OUTPUT | |
| echo "emoji=β" >> $GITHUB_OUTPUT | |
| echo "color=danger" >> $GITHUB_OUTPUT | |
| echo "color_int=15158332" >> $GITHUB_OUTPUT | |
| FAILED="" | |
| [[ "$STATIC" != "success" ]] && FAILED="${FAILED}Static Analysis(${STATIC}) " | |
| [[ "$BUILD" != "success" ]] && FAILED="${FAILED}Release Build(${BUILD})" | |
| echo "label=Nightly λΉλ μ€ν¨ β ${FAILED}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Notify Slack | |
| continue-on-error: true | |
| uses: slackapi/slack-github-action@v2.0.0 | |
| with: | |
| webhook: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| webhook-type: incoming-webhook | |
| payload: | | |
| { | |
| "text": "${{ steps.result.outputs.emoji }} *${{ steps.result.outputs.label }}*", | |
| "attachments": [{ | |
| "color": "${{ steps.result.outputs.color }}", | |
| "fields": [ | |
| { "title": "Static Analysis", "value": "${{ needs.static-analysis.result }}", "short": true }, | |
| { "title": "Release Build", "value": "${{ needs.release-build.result }}", "short": true }, | |
| { "title": "Run", | |
| "value": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", | |
| "short": false } | |
| ] | |
| }] | |
| } | |
| - name: Notify Discord | |
| env: | |
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| LABEL: ${{ steps.result.outputs.label }} | |
| COLOR: ${{ steps.result.outputs.color_int }} | |
| STATIC_RESULT: ${{ needs.static-analysis.result }} | |
| BUILD_RESULT: ${{ needs.release-build.result }} | |
| run: | | |
| curl -s -X POST "$DISCORD_WEBHOOK" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"embeds\": [{ | |
| \"title\": \"$LABEL\", | |
| \"color\": $COLOR, | |
| \"fields\": [ | |
| { \"name\": \"Static Analysis\", \"value\": \"$STATIC_RESULT\", \"inline\": true }, | |
| { \"name\": \"Release Build\", \"value\": \"$BUILD_RESULT\", \"inline\": true }, | |
| { \"name\": \"Run\", | |
| \"value\": \"[View Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\" } | |
| ] | |
| }] | |
| }" | |