diff --git a/.github/actions/setup-keystore/action.yml b/.github/actions/setup-keystore/action.yml new file mode 100644 index 00000000000..c0af173b371 --- /dev/null +++ b/.github/actions/setup-keystore/action.yml @@ -0,0 +1,113 @@ +name: 'Setup Keystore' +description: 'Decode and setup keystore for signing Android builds' + +inputs: + keystore-type: + description: 'Type of keystore to setup (debug|prerelease|internal|public)' + required: true + keystore-path: + description: 'Path where keystore should be placed' + required: false + default: '${{ runner.temp }}/keystore' + +outputs: + keystore-file-path: + description: 'Full path to the decoded keystore file' + value: ${{ steps.setup.outputs.keystore-file-path }} + +runs: + using: 'composite' + steps: + - name: Validate keystore type + shell: bash + run: | + case "${{ inputs.keystore-type }}" in + debug|prerelease|internal|public) + echo "Valid keystore type: ${{ inputs.keystore-type }}" + ;; + *) + echo "Error: Invalid keystore type '${{ inputs.keystore-type }}'. Must be one of: debug, prerelease, internal, public" + exit 1 + ;; + esac + + - name: Setup keystore directory + shell: bash + run: | + mkdir -p "${{ inputs.keystore-path }}" + + - name: Decode keystore + id: setup + shell: bash + env: + KEYSTORE_TYPE: ${{ inputs.keystore-type }} + KEYSTORE_PATH: ${{ inputs.keystore-path }} + run: | + # Map keystore type to secret name + case "${KEYSTORE_TYPE}" in + debug) + SECRET_NAME="ENCODED_KEYSTORE_DEBUG" + ;; + prerelease) + SECRET_NAME="ENCODED_KEYSTORE_PRE_RELEASE" + ;; + internal) + SECRET_NAME="ENCODED_KEYSTORE_INTERNAL_RELEASE" + ;; + public) + SECRET_NAME="ENCODED_KEYSTORE_PUBLIC_RELEASE" + ;; + esac + + # Get the encoded keystore from environment + ENCODED_KEYSTORE=$(printenv "${SECRET_NAME}" || echo "") + + if [ -z "${ENCODED_KEYSTORE}" ]; then + echo "Error: Secret ${SECRET_NAME} not found or empty" + exit 1 + fi + + # Decode keystore to file + KEYSTORE_FILE="${KEYSTORE_PATH}/the.keystore" + echo "${ENCODED_KEYSTORE}" | base64 -d > "${KEYSTORE_FILE}" + + # Verify file was created + if [ ! -f "${KEYSTORE_FILE}" ]; then + echo "Error: Failed to create keystore file" + exit 1 + fi + + echo "Keystore decoded successfully to: ${KEYSTORE_FILE}" + echo "keystore-file-path=${KEYSTORE_FILE}" >> $GITHUB_OUTPUT + + - name: Set keystore environment variables + shell: bash + env: + KEYSTORE_TYPE: ${{ inputs.keystore-type }} + KEYSTORE_FILE_PATH: ${{ steps.setup.outputs.keystore-file-path }} + run: | + # Set common environment variables + echo "KEYSTORE_FILE_PATH_DEBUG=${KEYSTORE_FILE_PATH}" >> $GITHUB_ENV + echo "KEYSTORE_FILE_PATH_RELEASE=${KEYSTORE_FILE_PATH}" >> $GITHUB_ENV + echo "KEYSTORE_FILE_PATH_COMPAT=${KEYSTORE_FILE_PATH}" >> $GITHUB_ENV + echo "KEYSTORE_FILE_PATH_COMPAT_RELEASE=${KEYSTORE_FILE_PATH}" >> $GITHUB_ENV + + # Set type-specific alias and password environment variables + case "${KEYSTORE_TYPE}" in + debug) + echo "Using debug keystore configuration" + # Debug keystore env vars will be set by secrets in calling workflow + ;; + prerelease) + echo "Using pre-release keystore configuration" + # Pre-release keystore env vars will be set by secrets in calling workflow + ;; + internal) + echo "Using internal release keystore configuration" + # Internal keystore env vars will be set by secrets in calling workflow + ;; + public) + echo "Using public release keystore configuration" + # Public keystore env vars will be set by secrets in calling workflow + ;; + esac diff --git a/.github/workflows/build-beta-app.yml b/.github/workflows/build-beta-app.yml.disabled similarity index 100% rename from .github/workflows/build-beta-app.yml rename to .github/workflows/build-beta-app.yml.disabled diff --git a/.github/workflows/build-develop-app.yml b/.github/workflows/build-develop-app.yml.disabled similarity index 100% rename from .github/workflows/build-develop-app.yml rename to .github/workflows/build-develop-app.yml.disabled diff --git a/.github/workflows/build-develop-unified.yml b/.github/workflows/build-develop-unified.yml new file mode 100644 index 00000000000..e3c6964d668 --- /dev/null +++ b/.github/workflows/build-develop-unified.yml @@ -0,0 +1,72 @@ +name: "Develop build (Unified)" + +on: + push: + branches: + - develop + merge_group: + types: [ checks_requested ] + branches: [ develop ] + pull_request: + branches: + - develop + types: [ opened, synchronize ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_sha }} + cancel-in-progress: true + +jobs: + # Build for PR and merge group validation (no store deployment) + build-validation: + if: github.event_name == 'pull_request' || github.event_name == 'merge_group' + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Dev", + "variant": "Debug", + "keystore-type": "debug", + "build-type": "apk", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}]" + }, + { + "flavor": "Internal", + "variant": "Compat", + "keystore-type": "internal", + "build-type": "bundle", + "generate-version-file": false, + "deployment-targets": "[]" + } + ] + enable-deployment: true + secrets: inherit + + # Build for direct pushes to develop (includes internal store deployment) + build-develop: + if: github.event_name == 'push' && github.ref_name == 'develop' + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Dev", + "variant": "Debug", + "keystore-type": "debug", + "build-type": "apk", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}]" + }, + { + "flavor": "Internal", + "variant": "Compat", + "keystore-type": "internal", + "build-type": "bundle", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}, {\"type\": \"google-play\", \"package-name\": \"com.wire.internal\", \"track\": \"internal\", \"status\": \"completed\"}]" + } + ] + enable-deployment: true + secrets: inherit diff --git a/.github/workflows/build-edge-env.yml b/.github/workflows/build-edge-env.yml.disabled similarity index 100% rename from .github/workflows/build-edge-env.yml rename to .github/workflows/build-edge-env.yml.disabled diff --git a/.github/workflows/build-fdroid-app.yml b/.github/workflows/build-fdroid-app.yml.disabled similarity index 100% rename from .github/workflows/build-fdroid-app.yml rename to .github/workflows/build-fdroid-app.yml.disabled diff --git a/.github/workflows/build-main-unified.yml b/.github/workflows/build-main-unified.yml new file mode 100644 index 00000000000..08b290f7b30 --- /dev/null +++ b/.github/workflows/build-main-unified.yml @@ -0,0 +1,53 @@ +name: "Main build (Unified)" + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_call: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate-trigger: + runs-on: ubuntu-latest + outputs: + should-build: ${{ steps.check.outputs.should-build }} + steps: + - name: Check if should build + id: check + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "Pull request detected - skipping build" + echo "should-build=false" >> $GITHUB_OUTPUT + elif [[ "${{ github.ref }}" != "refs/heads/main" ]]; then + echo "Not main branch - skipping build" + echo "should-build=false" >> $GITHUB_OUTPUT + else + echo "Push to main branch - proceeding with build" + echo "should-build=true" >> $GITHUB_OUTPUT + fi + + build-beta: + needs: validate-trigger + if: needs.validate-trigger.outputs.should-build == 'true' + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Beta", + "variant": "Release", + "keystore-type": "prerelease", + "build-type": "both", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}, {\"type\": \"google-play\", \"package-name\": \"com.wire.android.internal\", \"track\": \"internal\"}]" + } + ] + enable-deployment: true + secrets: inherit diff --git a/.github/workflows/build-prod-app.yml b/.github/workflows/build-prod-app.yml.disabled similarity index 100% rename from .github/workflows/build-prod-app.yml rename to .github/workflows/build-prod-app.yml.disabled diff --git a/.github/workflows/build-production-unified.yml b/.github/workflows/build-production-unified.yml new file mode 100644 index 00000000000..6d311dd06d8 --- /dev/null +++ b/.github/workflows/build-production-unified.yml @@ -0,0 +1,69 @@ +name: "Production build (Unified)" + +on: + release: + types: [ published ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.release.tag_name }} + cancel-in-progress: true + +jobs: + validate-release: + runs-on: ubuntu-latest + steps: + - name: Get latest release tag + id: get_latest_release + run: | + latest_tag=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name') + echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT + + - name: Compare versions + run: | + current_tag="${{ github.event.release.tag_name }}" + latest_tag="${{ steps.get_latest_release.outputs.latest_tag }}" + cur_ver="${current_tag#v}" + lat_ver="${latest_tag#v}" + highest="$(printf '%s\n%s' "$cur_ver" "$lat_ver" | sort -V | tail -n1)" + if [[ "$cur_ver" != "$lat_ver" && "$highest" != "$cur_ver" ]]; then + echo "Current tag ($current_tag) is lower than latest tag ($latest_tag). Failing the workflow." + exit 1 + else + echo "Current tag ($current_tag) is equal or higher than $latest_tag. Continuing the workflow." + fi + + build-production: + needs: validate-release + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Prod", + "variant": "Compatrelease", + "keystore-type": "public", + "build-type": "both", + "generate-version-file": true, + "deployment-targets": "[{\"type\": \"s3\"}, {\"type\": \"google-play\", \"package-name\": \"com.wire\", \"track\": \"alpha\"}, {\"type\": \"github-release\", \"additional-files\": \"app/version.txt\"}]" + } + ] + enable-deployment: true + secrets: inherit + + build-fdroid: + needs: validate-release + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Fdroid", + "variant": "Compatrelease", + "keystore-type": "public", + "build-type": "apk", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}, {\"type\": \"github-release\"}]" + } + ] + enable-deployment: true + secrets: inherit diff --git a/.github/workflows/build-rc-app.yml b/.github/workflows/build-rc-app.yml.disabled similarity index 100% rename from .github/workflows/build-rc-app.yml rename to .github/workflows/build-rc-app.yml.disabled diff --git a/.github/workflows/build-release-candidate-unified.yml b/.github/workflows/build-release-candidate-unified.yml new file mode 100644 index 00000000000..9bc235fd52e --- /dev/null +++ b/.github/workflows/build-release-candidate-unified.yml @@ -0,0 +1,64 @@ +name: "Release Candidate build (Unified)" + +on: + push: + branches: + - release/candidate + merge_group: + types: [ checks_requested ] + branches: [ release/candidate ] + pull_request: + branches: + - release/candidate + types: [ opened, synchronize ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_sha }} + cancel-in-progress: true + +jobs: + # Build for PR and merge group validation + build-validation: + if: github.event_name == 'merge_group' || (github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request') + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Staging", + "variant": "Compat", + "keystore-type": "internal", + "build-type": "apk", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}]" + } + ] + enable-deployment: true + secrets: inherit + + # Build for direct pushes to release/candidate + build-release-candidate: + if: github.event_name == 'push' && github.ref_name == 'release/candidate' + uses: ./.github/workflows/build-unified.yml + with: + build-config: | + [ + { + "flavor": "Staging", + "variant": "Compat", + "keystore-type": "internal", + "build-type": "apk", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}]" + }, + { + "flavor": "Internal", + "variant": "Compat", + "keystore-type": "internal", + "build-type": "both", + "generate-version-file": false, + "deployment-targets": "[{\"type\": \"s3\"}, {\"type\": \"google-play\", \"package-name\": \"com.wire.internal\", \"track\": \"production\", \"status\": \"completed\"}]" + } + ] + enable-deployment: true + secrets: inherit diff --git a/.github/workflows/build-unified.yml b/.github/workflows/build-unified.yml new file mode 100644 index 00000000000..782187aa6d8 --- /dev/null +++ b/.github/workflows/build-unified.yml @@ -0,0 +1,163 @@ +name: "Build Wire Android (Unified)" + +on: + workflow_call: + inputs: + build-config: + description: "JSON build configuration matrix" + required: true + type: string + enable-deployment: + description: "Enable deployment after successful build" + required: false + type: boolean + default: true + +concurrency: + group: build-unified-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Run quality gates first - builds only proceed if ALL quality gates pass + quality-gates: + name: "Quality Gates" + uses: ./.github/workflows/quality-gates.yml + + # Build matrix job - only runs if quality gates pass + build: + name: "Build ${{ matrix.flavor }}-${{ matrix.variant }}" + needs: quality-gates + if: needs.quality-gates.outputs.quality-passed == 'success' + runs-on: ubuntu-latest + strategy: + fail-fast: false # Continue building other flavors even if one fails + matrix: + include: ${{ fromJson(inputs.build-config) }} + + steps: + - name: Quality gates passed - starting build + run: | + echo "## Build Starting" >> $GITHUB_STEP_SUMMARY + echo "✅ Quality gates passed successfully" >> $GITHUB_STEP_SUMMARY + echo "🚀 Building ${{ matrix.flavor }}${{ matrix.variant }}..." >> $GITHUB_STEP_SUMMARY + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Set up JDK 17 + uses: buildjet/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Setup Keystore + uses: ./.github/actions/setup-keystore + with: + keystore-type: ${{ matrix.keystore-type }} + env: + ENCODED_KEYSTORE_DEBUG: ${{ secrets.ENCODED_KEYSTORE_DEBUG }} + ENCODED_KEYSTORE_PRE_RELEASE: ${{ secrets.ENCODED_KEYSTORE_PRE_RELEASE }} + ENCODED_KEYSTORE_INTERNAL_RELEASE: ${{ secrets.ENCODED_KEYSTORE_INTERNAL_RELEASE }} + ENCODED_KEYSTORE_PUBLIC_RELEASE: ${{ secrets.ENCODED_KEYSTORE_PUBLIC_RELEASE }} + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Set build environment variables + run: | + echo "Building ${{ matrix.flavor }}${{ matrix.variant }}" + + # Set keystore environment variables based on keystore type + case "${{ matrix.keystore-type }}" in + debug) + echo "KEYSTORE_KEY_NAME_DEBUG=${{ secrets.SIGNING_KEY_ALIAS_DEBUG }}" >> $GITHUB_ENV + echo "KEYPWD_DEBUG=${{ secrets.SIGNING_KEY_PASSWORD_DEBUG }}" >> $GITHUB_ENV + echo "KEYSTOREPWD_DEBUG=${{ secrets.SIGNING_STORE_PASSWORD_DEBUG }}" >> $GITHUB_ENV + ;; + prerelease) + echo "KEYSTORE_KEY_NAME_RELEASE=${{ secrets.SIGNING_KEY_ALIAS_PRE_RELEASE }}" >> $GITHUB_ENV + echo "KEYPWD_RELEASE=${{ secrets.SIGNING_KEY_PASSWORD_PRE_RELEASE }}" >> $GITHUB_ENV + echo "KEYSTOREPWD_RELEASE=${{ secrets.SIGNING_STORE_PASSWORD_PRE_RELEASE }}" >> $GITHUB_ENV + ;; + internal) + echo "KEYSTORE_KEY_NAME_COMPAT=${{ secrets.SIGNING_KEY_ALIAS_INTERNAL_RELEASE }}" >> $GITHUB_ENV + echo "KEYPWD_COMPAT=${{ secrets.SIGNING_KEY_PASSWORD_INTERNAL_RELEASE }}" >> $GITHUB_ENV + echo "KEYSTOREPWD_COMPAT=${{ secrets.SIGNING_STORE_PASSWORD_INTERNAL_RELEASE }}" >> $GITHUB_ENV + echo "KEYSTORE_KEY_NAME_COMPAT_RELEASE=${{ secrets.SIGNING_KEY_ALIAS_INTERNAL_RELEASE }}" >> $GITHUB_ENV + echo "KEYPWD_COMPAT_RELEASE=${{ secrets.SIGNING_KEY_PASSWORD_INTERNAL_RELEASE }}" >> $GITHUB_ENV + echo "KEYSTOREPWD_COMPAT_RELEASE=${{ secrets.SIGNING_STORE_PASSWORD_INTERNAL_RELEASE }}" >> $GITHUB_ENV + ;; + public) + echo "KEYSTORE_KEY_NAME_COMPAT_RELEASE=${{ secrets.SIGNING_KEY_ALIAS_PUBLIC_RELEASE }}" >> $GITHUB_ENV + echo "KEYPWD_COMPAT_RELEASE=${{ secrets.SIGNING_KEY_PASSWORD_PUBLIC_RELEASE }}" >> $GITHUB_ENV + echo "KEYSTOREPWD_COMPAT_RELEASE=${{ secrets.SIGNING_STORE_PASSWORD_PUBLIC_RELEASE }}" >> $GITHUB_ENV + ;; + esac + + - name: Build APK + if: matrix.build-type == 'apk' || matrix.build-type == 'both' + run: | + ./gradlew app:assemble${{ matrix.flavor }}${{ matrix.variant }} + env: + DATADOG_APP_ID: ${{ secrets.DATADOG_APP_ID }} + DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }} + ENABLE_SIGNING: ${{ secrets.ENABLE_SIGNING }} + + - name: Build AAB + if: matrix.build-type == 'bundle' || matrix.build-type == 'both' + run: | + ./gradlew app:bundle${{ matrix.flavor }}${{ matrix.variant }} + env: + DATADOG_APP_ID: ${{ secrets.DATADOG_APP_ID }} + DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }} + ENABLE_SIGNING: ${{ secrets.ENABLE_SIGNING }} + + - name: Generate version file + if: matrix.generate-version-file == true + run: ./gradlew generateVersionFile + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.flavor }}-${{ matrix.variant }}-artifacts + path: | + app/build/outputs/ + ${{ matrix.generate-version-file == true ? 'app/version.txt' : '' }} + retention-days: 7 + + - name: Build summary + run: | + echo "## Build Summary" >> $GITHUB_STEP_SUMMARY + echo "**Flavor**: ${{ matrix.flavor }}" >> $GITHUB_STEP_SUMMARY + echo "**Variant**: ${{ matrix.variant }}" >> $GITHUB_STEP_SUMMARY + echo "**Keystore**: ${{ matrix.keystore-type }}" >> $GITHUB_STEP_SUMMARY + echo "**Build Type**: ${{ matrix.build-type }}" >> $GITHUB_STEP_SUMMARY + + # List generated artifacts + echo "**Generated Artifacts**:" >> $GITHUB_STEP_SUMMARY + find app/build/outputs/ -name "*.apk" -o -name "*.aab" | head -10 | while read file; do + echo "- \`$(basename "$file")\`" >> $GITHUB_STEP_SUMMARY + done + + # Deploy job (conditional) + deploy: + needs: build + if: inputs.enable-deployment == true + strategy: + matrix: + include: ${{ fromJson(inputs.build-config) }} + uses: ./.github/workflows/deploy.yml + with: + artifacts-path: app/build/outputs/ + deployment-targets: ${{ matrix.deployment-targets }} + build-flavor: ${{ matrix.flavor }} + build-variant: ${{ matrix.variant }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} + SERVICE_ACCOUNT_JSON: ${{ secrets.SERVICE_ACCOUNT_JSON }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000000..4d43790c831 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,96 @@ +name: "Deploy" + +on: + workflow_call: + inputs: + artifacts-path: + description: "Path to build artifacts" + required: true + type: string + deployment-targets: + description: "JSON array of deployment targets" + required: true + type: string + build-flavor: + description: "Build flavor (dev, staging, internal, beta, prod, fdroid)" + required: true + type: string + build-variant: + description: "Build variant (debug, release, compat, compatrelease)" + required: true + type: string + secrets: + AWS_ACCESS_KEY_ID: + required: false + AWS_SECRET_ACCESS_KEY: + required: false + AWS_S3_BUCKET: + required: false + SERVICE_ACCOUNT_JSON: + required: false + +jobs: + deploy: + strategy: + matrix: + target: ${{ fromJson(inputs.deployment-targets) }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.build-flavor }}-${{ inputs.build-variant }}-artifacts + path: ${{ inputs.artifacts-path }} + + - name: Convert to lowercase + id: lowercase + run: | + echo "build-flavour=$(echo '${{ inputs.build-flavor }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + echo "build-variant=$(echo '${{ inputs.build-variant }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + + - name: Deploy to S3 + if: matrix.target.type == 's3' + uses: ./.github/actions/deploy-to-s3 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-bucket: ${{ secrets.AWS_S3_BUCKET }} + github-token: ${{ secrets.GITHUB_TOKEN }} + build-flavour: ${{ steps.lowercase.outputs.build-flavour }} + build-variant: ${{ steps.lowercase.outputs.build-variant }} + + - name: Deploy to Google Play + if: matrix.target.type == 'google-play' + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJson: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: ${{ matrix.target.package-name }} + releaseFiles: ${{ inputs.artifacts-path }}/bundle/${{ steps.lowercase.outputs.build-flavour }}${{ steps.lowercase.outputs.build-variant }}/*.aab + track: ${{ matrix.target.track }} + status: ${{ matrix.target.status || 'completed' }} + + - name: Deploy to GitHub Release + if: matrix.target.type == 'github-release' + uses: softprops/action-gh-release@v2.2.2 + with: + files: | + ${{ inputs.artifacts-path }}/apk/${{ steps.lowercase.outputs.build-flavour }}/${{ steps.lowercase.outputs.build-variant }}/*.apk + ${{ matrix.target.additional-files || '' }} + tag_name: ${{ github.event.release.tag_name }} + name: ${{ github.event.release.name }} + body: ${{ github.event.release.body }} + + deployment-summary: + needs: deploy + runs-on: ubuntu-latest + if: always() + steps: + - name: Create deployment summary + run: | + echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "**Build**: ${{ inputs.build-flavor }}${{ inputs.build-variant }}" >> $GITHUB_STEP_SUMMARY + echo "**Targets**: ${{ inputs.deployment-targets }}" >> $GITHUB_STEP_SUMMARY + echo "**Status**: ${{ needs.deploy.result }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml new file mode 100644 index 00000000000..c381c5c68c4 --- /dev/null +++ b/.github/workflows/quality-gates.yml @@ -0,0 +1,57 @@ +name: "Quality Gates" + +on: + workflow_call: + outputs: + quality-passed: + description: "Quality gates status" + value: ${{ jobs.validate.outputs.result }} + +concurrency: + group: quality-gates-${{ github.ref }} + cancel-in-progress: true + +jobs: + code-analysis: + name: "Code Analysis" + uses: ./.github/workflows/code-analysis.yml + + ui-tests: + name: "UI Tests" + uses: ./.github/workflows/gradle-run-ui-tests.yml + + unit-tests: + name: "Unit Tests & Coverage" + uses: ./.github/workflows/gradle-run-unit-tests.yml + secrets: inherit + + validate: + name: "Validate Quality Gates" + needs: [code-analysis, ui-tests, unit-tests] + runs-on: ubuntu-latest + outputs: + result: ${{ steps.result.outputs.status }} + steps: + - name: Check quality gate results + id: result + run: | + echo "## Quality Gates Summary" >> $GITHUB_STEP_SUMMARY + echo "✅ Code Analysis: Passed" >> $GITHUB_STEP_SUMMARY + echo "✅ UI Tests: Passed" >> $GITHUB_STEP_SUMMARY + echo "✅ Unit Tests & Coverage: Passed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "🎉 All quality gates passed! Proceeding with builds..." >> $GITHUB_STEP_SUMMARY + + echo "All quality gates passed successfully" + echo "status=success" >> $GITHUB_OUTPUT + + - name: Quality gates failed + if: failure() + run: | + echo "## Quality Gates Summary" >> $GITHUB_STEP_SUMMARY + echo "❌ One or more quality gates failed!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "🚫 Builds will not proceed until all quality gates pass." >> $GITHUB_STEP_SUMMARY + + echo "Quality gates failed - builds will not proceed" + exit 1