chore(android): ignore mlc4j runtime library #6
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
| # Nimittam Gallery - Comprehensive CI/CD Pipeline | |
| # Production-grade CI with performance monitoring, automated gates, and quality enforcement | |
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| paths: | |
| - 'Android/**' | |
| - '.github/workflows/**' | |
| pull_request: | |
| branches: [ main, develop ] | |
| paths: | |
| - 'Android/**' | |
| env: | |
| # Bundle size budgets (in MB) | |
| DEBUG_BUNDLE_MAX_SIZE: 50 | |
| RELEASE_BUNDLE_MAX_SIZE: 30 | |
| # Performance thresholds | |
| MIN_TEST_COVERAGE: 80 | |
| MAX_APK_SIZE_MB: 35 | |
| # Gradle optimization | |
| GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2 -Dorg.gradle.parallel=true" | |
| jobs: | |
| # ============================================================================= | |
| # Job 1: Code Quality & Static Analysis | |
| # ============================================================================= | |
| code-quality: | |
| name: Code Quality Checks | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| with: | |
| gradle-version: wrapper | |
| cache-read-only: ${{ github.event_name == 'pull_request' }} | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # ktlint Check | |
| - name: Run ktlint | |
| run: ./gradlew ktlintCheck --continue || true | |
| # Detekt Static Analysis | |
| - name: Run Detekt | |
| run: ./gradlew detekt --continue || true | |
| # Android Lint | |
| - name: Run Android Lint | |
| run: ./gradlew lintDebug --continue | |
| # Upload lint reports | |
| - name: Upload Lint Reports | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: lint-reports | |
| path: | | |
| Android/src/app/build/reports/lint-results-debug.html | |
| Android/src/app/build/reports/detekt.html | |
| retention-days: 7 | |
| # ============================================================================= | |
| # Job 2: Unit Tests with Coverage | |
| # ============================================================================= | |
| unit-tests: | |
| name: Unit Tests & Coverage | |
| runs-on: ubuntu-latest | |
| needs: code-quality | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # Run Unit Tests | |
| - name: Run Unit Tests | |
| run: ./gradlew testDebugUnitTest --continue | |
| # Generate Coverage Report | |
| - name: Generate Coverage Report | |
| run: ./gradlew jacocoTestReport --continue || true | |
| # Upload Test Results | |
| - name: Upload Test Results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: unit-test-results | |
| path: | | |
| Android/src/app/build/test-results/testDebugUnitTest/ | |
| Android/src/app/build/reports/tests/testDebugUnitTest/ | |
| retention-days: 7 | |
| # Upload Coverage Report | |
| - name: Upload Coverage Report | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage-report | |
| path: | | |
| Android/src/app/build/reports/jacoco/ | |
| retention-days: 7 | |
| # Parse Coverage and Fail if Below Threshold | |
| - name: Check Coverage Threshold | |
| run: | | |
| # Try to extract coverage percentage from Jacoco report | |
| COVERAGE_FILE="Android/src/app/build/reports/jacoco/jacocoTestReport/html/index.html" | |
| if [ -f "$COVERAGE_FILE" ]; then | |
| # Extract coverage percentage (simplified parsing) | |
| COVERAGE=$(grep -oP 'Total[^%]+%' "$COVERAGE_FILE" | grep -oP '\d+' | head -1) | |
| if [ -n "$COVERAGE" ] && [ "$COVERAGE" -lt "${{ env.MIN_TEST_COVERAGE }}" ]; then | |
| echo "❌ Code coverage ($COVERAGE%) is below threshold (${{ env.MIN_TEST_COVERAGE }}%)" | |
| exit 1 | |
| else | |
| echo "✅ Code coverage ($COVERAGE%) meets threshold (${{ env.MIN_TEST_COVERAGE }}%)" | |
| fi | |
| else | |
| echo "⚠️ Coverage report not found, skipping coverage check" | |
| fi | |
| continue-on-error: true | |
| # ============================================================================= | |
| # Job 3: Build Verification | |
| # ============================================================================= | |
| build-verification: | |
| name: Build Verification | |
| runs-on: ubuntu-latest | |
| needs: code-quality | |
| strategy: | |
| matrix: | |
| build-type: [debug, release] | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # Build APK | |
| - name: Build ${{ matrix.build-type }} APK | |
| run: | | |
| if [ "${{ matrix.build-type }}" == "release" ]; then | |
| ./gradlew assembleRelease | |
| else | |
| ./gradlew assembleDebug | |
| fi | |
| # Verify Build Artifacts | |
| - name: Verify Build Outputs | |
| run: | | |
| if [ "${{ matrix.build-type }}" == "release" ]; then | |
| ls -la app/build/outputs/apk/release/ | |
| ls -la app/build/outputs/bundle/release/ 2>/dev/null || echo "No AAB output" | |
| else | |
| ls -la app/build/outputs/apk/debug/ | |
| fi | |
| # Upload Build Artifacts | |
| - name: Upload Build Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.build-type }}-build | |
| path: | | |
| Android/src/app/build/outputs/apk/${{ matrix.build-type }}/ | |
| Android/src/app/build/outputs/bundle/${{ matrix.build-type }}/ | |
| retention-days: 7 | |
| # ============================================================================= | |
| # Job 4: Bundle Size Analysis & Budget Enforcement | |
| # ============================================================================= | |
| bundle-analysis: | |
| name: Bundle Size Analysis | |
| runs-on: ubuntu-latest | |
| needs: build-verification | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Need full history for comparison | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # Build Release APK for Analysis | |
| - name: Build Release APK | |
| run: ./gradlew assembleRelease | |
| # Analyze APK Size | |
| - name: Analyze APK Size | |
| id: apk-size | |
| run: | | |
| APK_PATH=$(find app/build/outputs/apk/release -name "*.apk" | head -1) | |
| if [ -n "$APK_PATH" ]; then | |
| APK_SIZE_BYTES=$(stat -f%z "$APK_PATH" 2>/dev/null || stat -c%s "$APK_PATH") | |
| APK_SIZE_MB=$((APK_SIZE_BYTES / 1024 / 1024)) | |
| echo "APK_SIZE_MB=$APK_SIZE_MB" >> $GITHUB_OUTPUT | |
| echo "APK_PATH=$APK_PATH" >> $GITHUB_OUTPUT | |
| echo "📦 APK Size: ${APK_SIZE_MB}MB" | |
| # Check against budget | |
| if [ "$APK_SIZE_MB" -gt "${{ env.RELEASE_BUNDLE_MAX_SIZE }}" ]; then | |
| echo "❌ APK size (${APK_SIZE_MB}MB) exceeds budget (${{ env.RELEASE_BUNDLE_MAX_SIZE }}MB)" | |
| exit 1 | |
| else | |
| echo "✅ APK size (${APK_SIZE_MB}MB) within budget (${{ env.RELEASE_BUNDLE_MAX_SIZE }}MB)" | |
| fi | |
| else | |
| echo "❌ No APK found" | |
| exit 1 | |
| fi | |
| # Download APK Analyzer | |
| - name: Setup APK Analyzer | |
| run: | | |
| # Create analysis script | |
| cat > analyze_apk.py << 'EOF' | |
| import os | |
| import subprocess | |
| import json | |
| import sys | |
| def analyze_apk(apk_path): | |
| """Analyze APK contents using aapt""" | |
| if not os.path.exists(apk_path): | |
| print(f"APK not found: {apk_path}") | |
| return None | |
| # Get file listing | |
| result = subprocess.run( | |
| ['unzip', '-l', apk_path], | |
| capture_output=True, | |
| text=True | |
| ) | |
| analysis = { | |
| 'total_size': os.path.getsize(apk_path), | |
| 'components': {} | |
| } | |
| # Parse output to categorize files | |
| for line in result.stdout.split('\n')[3:-2]: # Skip header/footer | |
| parts = line.split() | |
| if len(parts) >= 4: | |
| size = int(parts[0]) | |
| name = parts[3] | |
| if name.startswith('lib/'): | |
| arch = name.split('/')[1] if '/' in name else 'unknown' | |
| key = f'native_{arch}' | |
| elif name.startswith('res/'): | |
| key = 'resources' | |
| elif name.startswith('assets/'): | |
| key = 'assets' | |
| elif name.startswith('META-INF/'): | |
| key = 'signatures' | |
| elif name.endswith('.dex'): | |
| key = 'dex' | |
| elif name == 'AndroidManifest.xml': | |
| key = 'manifest' | |
| else: | |
| key = 'other' | |
| analysis['components'][key] = analysis['components'].get(key, 0) + size | |
| return analysis | |
| if __name__ == '__main__': | |
| apk_path = sys.argv[1] | |
| analysis = analyze_apk(apk_path) | |
| if analysis: | |
| print(json.dumps(analysis, indent=2)) | |
| # Write to file for artifact | |
| with open('apk_analysis.json', 'w') as f: | |
| json.dump(analysis, f, indent=2) | |
| # Print summary | |
| print("\n📊 APK Breakdown:") | |
| for component, size in sorted(analysis['components'].items(), | |
| key=lambda x: x[1], reverse=True): | |
| size_mb = size / (1024 * 1024) | |
| percentage = (size / analysis['total_size']) * 100 | |
| print(f" {component}: {size_mb:.2f}MB ({percentage:.1f}%)") | |
| else: | |
| sys.exit(1) | |
| EOF | |
| # Run APK Analysis | |
| - name: Run APK Analysis | |
| run: | | |
| APK_PATH=$(find app/build/outputs/apk/release -name "*.apk" | head -1) | |
| python3 analyze_apk.py "$APK_PATH" | |
| # Upload Analysis Results | |
| - name: Upload Bundle Analysis | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: bundle-analysis | |
| path: | | |
| Android/src/apk_analysis.json | |
| retention-days: 30 | |
| # Compare with baseline (if available) | |
| - name: Compare with Baseline | |
| run: | | |
| # Store current size for future comparison | |
| echo "${{ steps.apk-size.outputs.APK_SIZE_MB }}" > current_apk_size.txt | |
| echo "APK_SIZE_MB=${{ steps.apk-size.outputs.APK_SIZE_MB }}" >> $GITHUB_ENV | |
| # Post PR comment with size info | |
| - name: Post Size Report | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const size = process.env.APK_SIZE_MB; | |
| const budget = 30; | |
| const percentage = ((size / budget) * 100).toFixed(1); | |
| const body = `## 📦 APK Size Report | |
| | Metric | Value | Budget | Status | | |
| |--------|-------|--------|--------| | |
| | APK Size | ${size}MB | ${budget}MB | ${size > budget ? '❌ Exceeded' : '✅ Within'} | | |
| | Utilization | ${percentage}% | 100% | ${percentage > 90 ? '⚠️ High' : '✅ Good'} | | |
| ${size > budget ? '⚠️ **Warning:** APK size exceeds the budget. Please review added dependencies or assets.' : ''} | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| # ============================================================================= | |
| # Job 5: Baseline Profile Generation | |
| # ============================================================================= | |
| baseline-profile: | |
| name: Generate Baseline Profile | |
| runs-on: ubuntu-latest | |
| needs: build-verification | |
| if: github.ref == 'refs/heads/main' | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # Generate Baseline Profile | |
| - name: Generate Baseline Profile | |
| run: ./gradlew :app:generateBaselineProfile || echo "Baseline profile generation skipped (requires macrobenchmark module)" | |
| # Upload Generated Profile | |
| - name: Upload Baseline Profile | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: baseline-profile | |
| path: | | |
| Android/src/app/src/main/baseline-prof.txt | |
| retention-days: 30 | |
| # ============================================================================= | |
| # Job 6: Integration Tests | |
| # ============================================================================= | |
| integration-tests: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests, build-verification] | |
| strategy: | |
| matrix: | |
| api-level: [29, 33] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Grant execute permission for gradlew | |
| working-directory: ./Android/src | |
| run: chmod +x gradlew | |
| # Enable KVM for emulator acceleration | |
| - name: Enable KVM | |
| run: | | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| # Run Instrumented Tests | |
| - name: Run Instrumented Tests | |
| uses: reactivecircus/android-emulator-runner@v2 | |
| with: | |
| api-level: ${{ matrix.api-level }} | |
| target: default | |
| arch: x86_64 | |
| working-directory: ./Android/src | |
| script: ./gradlew connectedCheck --continue || true | |
| # Upload Test Results | |
| - name: Upload Instrumented Test Results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: instrumented-test-results-api${{ matrix.api-level }} | |
| path: | | |
| Android/src/app/build/outputs/androidTest-results/ | |
| Android/src/app/build/reports/androidTests/ | |
| retention-days: 7 | |
| # ============================================================================= | |
| # Job 7: Security Scan | |
| # ============================================================================= | |
| security-scan: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| needs: code-quality | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| # Run dependency vulnerability scan | |
| - name: Run OWASP Dependency Check | |
| run: | | |
| # Create a simple dependency check | |
| ./gradlew dependencies --configuration releaseRuntimeClasspath > dependencies.txt | |
| cat dependencies.txt | |
| continue-on-error: true | |
| # Upload dependency report | |
| - name: Upload Dependencies | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dependency-report | |
| path: Android/src/dependencies.txt | |
| retention-days: 7 | |
| # ============================================================================= | |
| # Job 8: Performance Regression Detection | |
| # ============================================================================= | |
| performance-check: | |
| name: Performance Regression Check | |
| runs-on: ubuntu-latest | |
| needs: build-verification | |
| defaults: | |
| run: | |
| working-directory: ./Android/src | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| # Build and measure build time | |
| - name: Measure Build Performance | |
| run: | | |
| echo "Measuring build performance..." | |
| START_TIME=$(date +%s) | |
| ./gradlew clean assembleRelease --profile | |
| END_TIME=$(date +%s) | |
| BUILD_TIME=$((END_TIME - START_TIME)) | |
| echo "BUILD_TIME_SECONDS=$BUILD_TIME" >> $GITHUB_ENV | |
| echo "⏱️ Build completed in ${BUILD_TIME}s" | |
| # Upload build scan/profile | |
| - name: Upload Build Profile | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-profile | |
| path: Android/src/build/reports/profile/ | |
| retention-days: 7 | |
| # ============================================================================= | |
| # Job 9: Final Status Report | |
| # ============================================================================= | |
| final-status: | |
| name: Final Status Report | |
| runs-on: ubuntu-latest | |
| needs: | |
| - code-quality | |
| - unit-tests | |
| - build-verification | |
| - bundle-analysis | |
| - integration-tests | |
| - security-scan | |
| - performance-check | |
| if: always() | |
| steps: | |
| - name: Generate Status Report | |
| run: | | |
| echo "## CI/CD Pipeline Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Code Quality | ${{ needs.code-quality.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Unit Tests | ${{ needs.unit-tests.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Build Verification | ${{ needs.build-verification.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Bundle Analysis | ${{ needs.bundle-analysis.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Integration Tests | ${{ needs.integration-tests.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Security Scan | ${{ needs.security-scan.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Performance Check | ${{ needs.performance-check.result }} |" >> $GITHUB_STEP_SUMMARY | |
| - name: Check Overall Status | |
| run: | | |
| if [ "${{ needs.code-quality.result }}" == "failure" ] || \ | |
| [ "${{ needs.unit-tests.result }}" == "failure" ] || \ | |
| [ "${{ needs.build-verification.result }}" == "failure" ] || \ | |
| [ "${{ needs.bundle-analysis.result }}" == "failure" ]; then | |
| echo "❌ Critical jobs failed" | |
| exit 1 | |
| else | |
| echo "✅ All critical jobs passed" | |
| fi |