diff --git a/README.md b/README.md index 1d15799..a049ef4 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,33 @@ jobs: | `keystore-path` | where the keystore should be placed | No | `release.keystore` | | `rock-build-extra-params` | Extra parameters for rock build:android | No | - | | `comment-bot` | Whether to comment PR with build link | No | `true` | -| `custom-ref` | Custom app reference for artifact naming | No | - | +| `custom-identifier` | Custom identifier used in artifact naming for re-sign and ad-hoc flows to distinguish builds with the same native fingerprint | No | - | + +## Artifact Naming + +The action uses two distinct naming strategies for uploads: + +### ZIP Artifacts (native build caching) + +ZIP artifacts store the native build for reuse. The naming depends on the flow: + +- **Ad-hoc flow** (`ad-hoc: true`): ZIP name uses **fingerprint only** — `rock-android-{variant}-{fingerprint}`. One ZIP per fingerprint, shared across all builds with the same native code. Skipped if already uploaded. +- **Non-ad-hoc re-sign flow** (e.g. `pull_request` with `re-sign: true`): ZIP name includes an **identifier** — `rock-android-{variant}-{identifier}-{fingerprint}`. Used as the distribution mechanism without adhoc builds. +- **Regular builds** (no `re-sign`): ZIP name uses **fingerprint only** `rock-android-{variant}-{fingerprint}` + +### Ad-Hoc Artifacts (distribution to testers) + +When `ad-hoc: true`, distribution files (APK + `index.html`) are uploaded under a name that **always includes an identifier**: `rock-android-{variant}-{identifier}-{fingerprint}`. This ensures every uploaded adhoc build can point to unique distribution URL based on `{identifier}`, even when multiple builds share the same native fingerprint. + +### Identifier Priority + +The identifier distinguishes builds that share the same native fingerprint (e.g., concurrent builds from different branches). +It is resolved in this order: +1. `custom-identifier` input — explicit value provided by the caller (e.g., commit SHA of the head of the PR branch) +2. PR number — automatically extracted from `pull_request` events +3. Short commit SHA — 7-character fallback for push events and dispatches + +> **Note:** The identifier becomes part of artifact names and S3 paths. Allowed characters: `a-z`, `A-Z`, `0-9`, `-`, `.`, `_`. Commas are used internally as trait delimiters and converted to hyphens (e.g., `debug,42` → `debug-42`), so they must not appear in the identifier. Spaces, slashes, and shell metacharacters are also not allowed. ## Outputs diff --git a/action.yml b/action.yml index dac4d0f..71a231e 100644 --- a/action.yml +++ b/action.yml @@ -68,8 +68,8 @@ inputs: description: 'Whether to send a comment under PR with the link to the generated build' required: false default: true - custom-ref: - description: 'Custom app reference for artifact naming' + custom-identifier: + description: 'Custom identifier used in artifact naming for re-sign and ad-hoc flows to distinguish builds with the same native fingerprint' required: false outputs: @@ -290,16 +290,28 @@ runs: shell: bash working-directory: ${{ inputs.working-directory }} - - name: Update Artifact Name for re-signed builds - if: ${{ env.ARTIFACT_URL && inputs.re-sign == 'true' }} + - name: Set Identifier + if: ${{ inputs.re-sign == 'true' || inputs.ad-hoc == 'true' }} run: | - if [ "${{ github.event_name }}" = "pull_request" ]; then + if [ -n "${{ inputs.custom-identifier }}" ]; then + IDENTIFIER="${{ inputs.custom-identifier }}" + if ! echo "$IDENTIFIER" | grep -qE '^[a-zA-Z0-9._-]+$'; then + echo "Invalid 'custom-identifier': '$IDENTIFIER'. Use only alphanumeric characters, hyphens, dots, and underscores." + exit 1 + fi + elif [ "${{ github.event_name }}" = "pull_request" ]; then IDENTIFIER="${{ github.event.pull_request.number }}" - elif [ -n "${{ inputs.custom-ref }}" ]; then - IDENTIFIER="${{ inputs.custom-ref }}" else IDENTIFIER=$(echo "$GITHUB_SHA" | cut -c1-7) fi + echo "IDENTIFIER=$IDENTIFIER" >> $GITHUB_ENV + shell: bash + + # Non-ad-hoc re-sign flow: add identifier to ARTIFACT_NAME so the re-signed ZIP Artifact doesn't overwrite the base ZIP. + # Skipped for ad-hoc — ARTIFACT_NAME stays fingerprint-only (one ZIP Artifact per fingerprint). + - name: Update Artifact Name for re-signed builds + if: ${{ env.ARTIFACT_URL && inputs.re-sign == 'true' && inputs.ad-hoc != 'true' }} + run: | ARTIFACT_TRAITS="${{ inputs.variant }},${IDENTIFIER}" ARTIFACT_TRAITS_HYPHENATED=$(echo "$ARTIFACT_TRAITS" | tr ',' '-') ARTIFACT_TRAITS_HYPHENATED_FINGERPRINT="${ARTIFACT_TRAITS_HYPHENATED}-${FINGERPRINT}" @@ -328,28 +340,45 @@ runs: path: ${{ env.ARTIFACT_PATH }} if-no-files-found: error + # Non-ad-hoc re-sign flow: upload ZIP Artifact with {identifier}-{fingerprint} name # For re-signed builds, the ARTIFACT_NAME may contain PR-number, while Rock will save the artifact without PR trait in its cache. # We need to upload the artifact with the PR-number in the name, that's why we use --binary-path with appropriate ARTIFACT_PATH that accounts for it. - - name: Upload Artifact to Remote Cache for re-signed builds - if: ${{ env.PROVIDER_NAME != 'GitHub' && inputs.re-sign == 'true' }} + - name: Upload re-signed ZIP Artifact for non-ad-hoc flow + if: ${{ env.PROVIDER_NAME != 'GitHub' && inputs.re-sign == 'true' && inputs.ad-hoc != 'true' }} run: | OUTPUT=$(npx rock remote-cache upload --name ${{ env.ARTIFACT_NAME }} --binary-path "${{ env.ARTIFACT_PATH }}" --json --verbose) || (echo "$OUTPUT" && exit 1) echo "ARTIFACT_URL=$(echo "$OUTPUT" | jq -r '.url')" >> $GITHUB_ENV shell: bash - - name: Upload Artifact to Remote Cache for regular builds - if: ${{ env.PROVIDER_NAME != 'GitHub' && inputs.re-sign != 'true' && !env.ARTIFACT_URL }} + # Upload ZIP Artifact with {fingerprint}-only name as base ZIP Artifact for caching only (first build only). + # Runs only when no cached artifact exists (!ARTIFACT_URL), meaning native build was done from scratch. + # Excludes non-ad-hoc re-sign, which uploads with identifier in the name. + # Applies to: + # - regular builds (no ad-hoc, no re-sign) + # - re-sign with ad-hoc + - name: Upload ZIP Artifact for caching + if: ${{ env.PROVIDER_NAME != 'GitHub' && !env.ARTIFACT_URL && (inputs.re-sign != 'true' || inputs.ad-hoc == 'true') }} run: | OUTPUT=$(npx rock remote-cache upload --name ${{ env.ARTIFACT_NAME }} --json --verbose) || (echo "$OUTPUT" && exit 1) echo "ARTIFACT_URL=$(echo "$OUTPUT" | jq -r '.url')" >> $GITHUB_ENV shell: bash - # For ad-hoc builds, the ARTIFACT_NAME may contain PR-number, while Rock will save the artifact without PR trait in its cache. - # We need to upload the artifact with the PR-number in the name, that's why we use --binary-path with appropriate ARTIFACT_PATH that accounts for it. + + # Ad-hoc uploads always include an identifier in the name {identifier}-{fingerprint} + - name: Set Ad-Hoc Artifact Name + if: ${{ inputs.ad-hoc == 'true' }} + run: | + ADHOC_TRAITS="${{ inputs.variant }},${IDENTIFIER}" + ADHOC_TRAITS_HYPHENATED=$(echo "$ADHOC_TRAITS" | tr ',' '-') + echo "ADHOC_ARTIFACT_NAME=rock-android-${ADHOC_TRAITS_HYPHENATED}-${FINGERPRINT}" >> $GITHUB_ENV + shell: bash + + # Uploads APK/AAB + index.html under ad-hoc/{ADHOC_ARTIFACT_NAME}/. + # The ARTIFACT_URL output points to index.html - name: Upload for Ad-hoc distribution if: ${{ env.PROVIDER_NAME != 'GitHub' && inputs.ad-hoc == 'true' }} run: | - OUTPUT=$(npx rock remote-cache upload --name ${{ env.ARTIFACT_NAME }} --binary-path "${{ env.ARTIFACT_PATH }}" --json --ad-hoc) || (echo "$OUTPUT" && exit 1) + OUTPUT=$(npx rock remote-cache upload --name ${{ env.ADHOC_ARTIFACT_NAME }} --binary-path "${{ env.ARTIFACT_PATH }}" --json --ad-hoc) || (echo "$OUTPUT" && exit 1) echo "ARTIFACT_URL=$(echo "$OUTPUT" | jq -r '.url')" >> $GITHUB_ENV shell: bash