Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
57 changes: 43 additions & 14 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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

Expand Down