Skip to content
Open
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
74 changes: 74 additions & 0 deletions BITRISE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Rock iOS Bitrise Step

This Bitrise step enables remote building of iOS applications using [Rock](https://rockjs.dev). It supports both simulator and device builds, with automatic artifact caching. Code signing should be configured separately in your workflow before using this step.

> [!NOTE]
> **Code Signing Required**: This step assumes that iOS code signing is already configured in your workflow before using this step. For device builds and re-signing, certificates and provisioning profiles must be available in the keychain. See the [Bitrise iOS Code Signing documentation](https://docs.bitrise.io/en/bitrise-ci/code-signing/ios-code-signing/ios-code-signing.html) for setup instructions.

## Usage

```yaml
---
format_version: '23'
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
project_type: react-native
meta:
bitrise.io:
stack: osx-xcode-16.4.x
machine_type_id: g2.mac.large
workflows:
rock-remote-build-ios:
description: 'Rock Remote Build - iOS'
envs:
- WORKING_DIRECTORY: "$BITRISE_SOURCE_DIR"
- DESTINATION: simulator
- SCHEME: RockRemoteBuildTest
- CONFIGURATION: Release
- RE_SIGN: 'false'
- AD_HOC: 'false'
- ROCK_BUILD_EXTRA_PARAMS: ''
- SIGNING_IDENTITY: ''
steps:
- activate-ssh-key@4: {}
- git-clone@8: {}
- npm@1:
title: npm install
inputs:
- workdir: "$WORKING_DIRECTORY"
- command: install
- git::https://github.com/callstackincubator/ios@main:
title: Rock Remote Build - iOS
inputs:
- WORKING_DIRECTORY: "$WORKING_DIRECTORY"
- DESTINATION: "$DESTINATION"
- SCHEME: "$SCHEME"
- CONFIGURATION: "$CONFIGURATION"
- RE_SIGN: "$RE_SIGN"
- AD_HOC: "$AD_HOC"
- ROCK_BUILD_EXTRA_PARAMS: "$ROCK_BUILD_EXTRA_PARAMS"
- SIGNING_IDENTITY: "$SIGNING_IDENTITY"
```

## Bitrise Inputs

| Input | Description | Required | Default |
| ----------------------------- | ------------------------------------------------------------------------------- | -------- | ----------- |
| `WORKING_DIRECTORY` | Working directory for the build command | No | `.` |
| `DESTINATION` | Build destination: "simulator" or "device" | Yes | `simulator` |
| `SCHEME` | Xcode scheme | Yes | - |
| `CONFIGURATION` | Xcode configuration | Yes | - |
| `RE_SIGN` | Re-sign the app bundle with new JS bundle. Requires certificates in keychain | No | `false` |
| `AD_HOC` | Upload the IPA for ad-hoc distribution to easily install on provisioned devices | No | `false` |
| `SIGNING_IDENTITY` | Code signing identity for re-signing. Auto-detects if not provided | No | - |
| `ROCK_BUILD_EXTRA_PARAMS` | Extra parameters for rock build:ios | No | - |

## Bitrise Outputs

| Output | Description |
| -------------- | ------------------------- |
| `ARTIFACT_URL` | URL of the build artifact |
| `ARTIFACT_ID` | ID of the build artifact |

## License

MIT
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Rock iOS GitHub Action
# Rock iOS Workflow

This GitHub Action enables remote building of iOS applications using [Rock](https://rockjs.dev). It supports both simulator and device builds, with automatic artifact caching and code signing capabilities.
This repository provides workflows for building iOS applications using [Rock](https://rockjs.dev). It supports both simulator and device builds, with automatic artifact caching and code signing capabilities.

## Features

Expand All @@ -11,7 +11,10 @@ This GitHub Action enables remote building of iOS applications using [Rock](http
- Native fingerprint-based caching
- Configurable build parameters

## Usage
> [!NOTE]
> **Looking for Bitrise?** See [BITRISE.md](./BITRISE.md) for Bitrise-specific documentation.

## GitHub Actions Usage

```yaml
name: iOS Build
Expand Down Expand Up @@ -44,7 +47,7 @@ jobs:
# ad-hoc: true
```

## Inputs
## GitHub Actions Inputs

| Input | Description | Required | Default |
| ----------------------------- | ------------------------------------------------------------------------------- | -------- | ----------- |
Expand All @@ -63,7 +66,7 @@ jobs:
| `rock-build-extra-params` | Extra parameters for rock build:ios | No | - |
| `comment-bot` | Whether to comment PR with build link | No | `true` |

## Outputs
## GitHub Actions Outputs

| Output | Description |
| -------------- | ------------------------- |
Expand Down
47 changes: 47 additions & 0 deletions bitrise.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
format_version: '23'
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
project_type: react-native
meta:
bitrise.io:
stack: osx-xcode-16.4.x
machine_type_id: g2.mac.large
workflows:
rock-remote-build-ios:
description: 'Install and build iOS app with Rock'
envs:
- WORKING_DIRECTORY: "$BITRISE_SOURCE_DIR"
- DESTINATION: simulator
- SCHEME: ReactNativeRocks
- CONFIGURATION: Release
- RE_SIGN: 'false'
- AD_HOC: 'false'
- ROCK_BUILD_EXTRA_PARAMS: ''
- SIGNING_IDENTITY: ''
steps:
- activate-ssh-key@4: {}
- git-clone@8: {}
- npm@1:
title: npm install
inputs:
- workdir: "$WORKING_DIRECTORY"
- command: install
- git::https://github.com/callstackincubator/ios@main:
title: Rock Remote Build - iOS
inputs:
- WORKING_DIRECTORY: "$WORKING_DIRECTORY"
- DESTINATION: "$DESTINATION"
- SCHEME: "$SCHEME"
- CONFIGURATION: "$CONFIGURATION"
- RE_SIGN: "$RE_SIGN"
- AD_HOC: "$AD_HOC"
- ROCK_BUILD_EXTRA_PARAMS: "$ROCK_BUILD_EXTRA_PARAMS"
- SIGNING_IDENTITY: "$SIGNING_IDENTITY"
- script@1:
title: Show Step Outputs
inputs:
- content: |-
#!/usr/bin/env bash
set -e
echo "ARTIFACT_URL=${ARTIFACT_URL:-<empty>}"
echo "ARTIFACT_ID=${ARTIFACT_ID:-<empty>}"
234 changes: 234 additions & 0 deletions step.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#!/usr/bin/env bash
set -euo pipefail

log() { echo "[$(date +'%H:%M:%S')] $*"; }
fail() { echo "ERROR: $*" >&2; exit 1; }

# Inputs mapped from the original GitHub Action
WORKING_DIRECTORY="${WORKING_DIRECTORY:-$BITRISE_SOURCE_DIR}"
DESTINATION="${DESTINATION:-simulator}"
SCHEME="${SCHEME:-}"
CONFIGURATION="${CONFIGURATION:-}"
RE_SIGN="${RE_SIGN:-}"
AD_HOC="${AD_HOC:-false}"

SIGNING_IDENTITY="${SIGNING_IDENTITY:-}"

ROCK_BUILD_EXTRA_PARAMS="${ROCK_BUILD_EXTRA_PARAMS:-}"


# Validate inputs (mirror composite action behavior)
if [ "$DESTINATION" != "simulator" ] && [ "$DESTINATION" != "device" ]; then
fail "Invalid input 'destination': '$DESTINATION'. Allowed values: 'simulator' or 'device'."
fi



# Fingerprint
pushd "$WORKING_DIRECTORY" >/dev/null
log "Generating fingerprint"
if ! FINGERPRINT_OUTPUT="$(npx rock fingerprint -p ios --raw)"; then
echo "$FINGERPRINT_OUTPUT"
fail "Fingerprint failed"
fi
FINGERPRINT="$FINGERPRINT_OUTPUT"
envman add --key FINGERPRINT --value "$FINGERPRINT"
popd >/dev/null

# Detect provider and fail early if GitHub (not supported from this Bitrise step)
pushd "$WORKING_DIRECTORY" >/dev/null
log "Detecting provider name"
if ! PROVIDER_NAME="$(npx rock remote-cache get-provider-name)"; then
echo "$PROVIDER_NAME"
fail "remote-cache get-provider-name failed"
fi
popd >/dev/null

if [ "$PROVIDER_NAME" = "GitHub" ]; then
fail "Provider 'GitHub' is not supported from this Bitrise step. Please configure Rock remote cache with a non-GitHub provider."
fi

# Artifact discovery
ARTIFACT_NAME="${ARTIFACT_NAME:-}"
ARTIFACT_URL="${ARTIFACT_URL:-}"
ARTIFACT_ID="${ARTIFACT_ID:-}"

# PR-specific artifact (avoid overwriting the main artifact with new JS bundle)
if [ "${BITRISE_GIT_EVENT_TYPE:-}" = "pull_request" ] && [ "$RE_SIGN" = "true" ]; then
ARTIFACT_TRAITS="${DESTINATION},${CONFIGURATION},${BITRISE_PULL_REQUEST:-}"
envman add --key ARTIFACT_TRAITS --value "$ARTIFACT_TRAITS"

OUTPUT=$(npx rock remote-cache list -p ios --traits "${ARTIFACT_TRAITS}" --json || (echo "$OUTPUT" && exit 1))
if [ -n "$OUTPUT" ]; then
ARTIFACT_URL="$(echo "$OUTPUT" | jq -r '.url')"
ARTIFACT_ID="$(echo "$OUTPUT" | jq -r '.id')"
ARTIFACT_NAME="$(echo "$OUTPUT" | jq -r '.name')"
envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
envman add --key ARTIFACT_ID --value "$ARTIFACT_ID"
envman add --key ARTIFACT_NAME --value "$ARTIFACT_NAME"
fi
fi

# Regular artifact
if [ -z "$ARTIFACT_NAME" ]; then
ARTIFACT_TRAITS="${DESTINATION},${CONFIGURATION}"
envman add --key ARTIFACT_TRAITS --value "$ARTIFACT_TRAITS"

OUTPUT=$(npx rock remote-cache list -p ios --traits "${ARTIFACT_TRAITS}" --json || (echo "$OUTPUT" && exit 1))
if [ -n "$OUTPUT" ]; then
ARTIFACT_URL="$(echo "$OUTPUT" | jq -r '.url')"
ARTIFACT_ID="$(echo "$OUTPUT" | jq -r '.id')"
ARTIFACT_NAME="$(echo "$OUTPUT" | jq -r '.name')"
envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
envman add --key ARTIFACT_ID --value "$ARTIFACT_ID"
envman add --key ARTIFACT_NAME --value "$ARTIFACT_NAME"
fi
fi

# Set Artifact Name (if not set)
if [ -z "$ARTIFACT_NAME" ]; then
ARTIFACT_TRAITS_HYPHENATED="$(echo "$ARTIFACT_TRAITS" | tr ',' '-')"
ARTIFACT_TRAITS_HYPHENATED_FINGERPRINT="${ARTIFACT_TRAITS_HYPHENATED}-${FINGERPRINT}"
ARTIFACT_NAME="rock-ios-${ARTIFACT_TRAITS_HYPHENATED_FINGERPRINT}"
envman add --key ARTIFACT_NAME --value "$ARTIFACT_NAME"
fi


# Build if no artifact was found
if [ -z "$ARTIFACT_URL" ]; then
pushd "$WORKING_DIRECTORY" >/dev/null
JSON_OUTPUT=$(npx rock config -p ios || (echo "$JSON_OUTPUT" && exit 1))
IOS_SOURCE_DIR="$(echo "$JSON_OUTPUT" | jq -r '.project.ios.sourceDir')"
log "Resolved ios.sourceDir: ${IOS_SOURCE_DIR:-<empty>}"
envman add --key IOS_SOURCE_DIR --value "$IOS_SOURCE_DIR"

# Build iOS with safe extra params handling (no eval)
BUILD_ARGS=(--scheme "$SCHEME" --configuration "$CONFIGURATION" --build-folder build --destination "$DESTINATION" --verbose)
if [ "$DESTINATION" = "device" ]; then
BUILD_ARGS+=("--archive")
fi

if [ -n "$ROCK_BUILD_EXTRA_PARAMS" ]; then
IFS=' ' read -r -a EXTRA_ARR <<< "$ROCK_BUILD_EXTRA_PARAMS"
BUILD_ARGS+=("${EXTRA_ARR[@]}")
fi

npx rock build:ios "${BUILD_ARGS[@]}"
popd >/dev/null

# Find Build Artifact
if [ "$DESTINATION" = "device" ]; then
IPA_PATH="$(find .rock/cache/ios/export -maxdepth 1 -name '*.ipa' -type f | head -1)"
echo "IPA_PATH $IPA_PATH"
envman add --key ARTIFACT_PATH --value "$IPA_PATH"
else
APP_PATH="$(find "$IOS_SOURCE_DIR"/build -name '*.app' -type d | head -1 )"
APP_DIR="$(dirname "$APP_PATH")"
APP_BASENAME="$(basename "$APP_PATH")"

ARTIFACT_PATH="$APP_DIR/app.tar.gz"
tar -C "$APP_DIR" -czvf "$ARTIFACT_PATH" "$APP_BASENAME"
envman add --key ARTIFACT_PATH --value "$ARTIFACT_PATH"
fi
fi

# PR re-sign: download and (re)sign/rebundle
if [ -n "$ARTIFACT_URL" ] && [ "$RE_SIGN" = "true" ] && [ "${BITRISE_GIT_EVENT_TYPE:-}" = "pull_request" ]; then
log "Downloading cached artifact from remote cache: name=$ARTIFACT_NAME"
DOWNLOAD_OUTPUT=$(npx rock remote-cache download --name "$ARTIFACT_NAME" --json || (echo "$DOWNLOAD_OUTPUT" && exit 1))
DL_PATH="$(echo "$DOWNLOAD_OUTPUT" | jq -r '.path')"

if [ "$DESTINATION" = "device" ]; then
envman add --key ARTIFACT_PATH --value "$DL_PATH"
log "Re-signing IPA with new JS bundle: path=$DL_PATH identity=${SIGNING_IDENTITY:-auto-detect}"
pushd "$WORKING_DIRECTORY" >/dev/null
if [ -n "$SIGNING_IDENTITY" ]; then
npx rock sign:ios "$DL_PATH" --build-jsbundle --identity "$SIGNING_IDENTITY"
else
npx rock sign:ios "$DL_PATH" --build-jsbundle
fi
popd >/dev/null
else
APP_DIR="$(dirname "$DL_PATH")"
log "Unpacking APP tarball: $DL_PATH"
tar -C "$APP_DIR" -xzf "$DL_PATH"
EXTRACTED_APP="$(find "$APP_DIR" -name '*.app' -type d | head -1)"
envman add --key ARTIFACT_PATH --value "$DL_PATH"
envman add --key ARTIFACT_TAR_PATH --value "$EXTRACTED_APP"

log "Re-bundling APP with new JS: app=$EXTRACTED_APP"
pushd "$WORKING_DIRECTORY" >/dev/null
npx rock sign:ios "$EXTRACTED_APP" --build-jsbundle --app
popd >/dev/null
fi

# Update Artifact Name for re-signed builds
ARTIFACT_TRAITS="${DESTINATION},${CONFIGURATION},${BITRISE_PULL_REQUEST:-}"
ARTIFACT_TRAITS_HYPHENATED="$(echo "$ARTIFACT_TRAITS" | tr ',' '-')"
ARTIFACT_TRAITS_HYPHENATED_FINGERPRINT="${ARTIFACT_TRAITS_HYPHENATED}-${FINGERPRINT}"
ARTIFACT_NAME="rock-ios-${ARTIFACT_TRAITS_HYPHENATED_FINGERPRINT}"
envman add --key ARTIFACT_NAME --value "$ARTIFACT_NAME"
envman add --key ARTIFACT_TRAITS --value "$ARTIFACT_TRAITS"
fi

# Find artifact URL again before uploading
OUTPUT=$(npx rock remote-cache list --name "$ARTIFACT_NAME" --json || (echo "$OUTPUT" && exit 1))
if [ -n "$OUTPUT" ]; then
ARTIFACT_URL="$(echo "$OUTPUT" | jq -r '.url')"
ARTIFACT_ID="$(echo "$OUTPUT" | jq -r '.id')"
envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
envman add --key ARTIFACT_ID --value "$ARTIFACT_ID"
fi

# Copy artifact to Bitrise Deploy dir (parity with "Upload Artifact to GitHub" intent)
if { [ -z "${ARTIFACT_URL:-}" ] || { [ "$RE_SIGN" = "true" ] && [ "${BITRISE_GIT_EVENT_TYPE:-}" = "pull_request" ]; }; } && [ -n "${ARTIFACT_PATH:-}" ]; then
if [ -n "${BITRISE_DEPLOY_DIR:-}" ]; then
TARGET_PATH="$BITRISE_DEPLOY_DIR/$ARTIFACT_NAME"
log "Copying artifact to Bitrise Deploy dir: $TARGET_PATH"
cp "$ARTIFACT_PATH" "$TARGET_PATH"
envman add --key UPLOAD_ARTIFACT_URL --value "${BITRISE_BUILD_URL:-}/artifacts"
envman add --key UPLOAD_ARTIFACT_ID --value "bitrise-$ARTIFACT_NAME"
fi
fi

# Upload to Remote Cache for re-signed builds (PR)
if [ "$RE_SIGN" = "true" ] && [ "${BITRISE_GIT_EVENT_TYPE:-}" = "pull_request" ] && [ -n "${ARTIFACT_PATH:-}" ]; then
OUTPUT=$(npx rock remote-cache upload --name "$ARTIFACT_NAME" --binary-path "$ARTIFACT_PATH" --json || (echo "$OUTPUT" && exit 1))
if [ -n "$OUTPUT" ]; then
ARTIFACT_URL="$(echo "$OUTPUT" | jq -r '.url')"
envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
fi
fi

# Upload to Remote Cache for regular builds
if [ -z "${ARTIFACT_URL:-}" ]; then
OUTPUT=$(npx rock remote-cache upload --name "$ARTIFACT_NAME" --json || (echo "$OUTPUT" && exit 1))
if [ -n "$OUTPUT" ]; then
ARTIFACT_URL="$(echo "$OUTPUT" | jq -r '.url')"
envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
fi
fi

# Upload for Ad-hoc distribution
if [ "$AD_HOC" = "true" ] && [ -n "${ARTIFACT_PATH:-}" ]; then
OUTPUT=$(npx rock remote-cache upload --name "$ARTIFACT_NAME" --binary-path "$ARTIFACT_PATH" --json --ad-hoc || (echo "$OUTPUT" && exit 1))
if [ -n "$OUTPUT" ]; then
ARTIFACT_URL="$(echo "$OUTPUT" | jq -r '.url')"
envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
fi
fi

# Delete Old Re-Signed Artifacts
if [ -n "${ARTIFACT_URL:-}" ] && [ "$RE_SIGN" = "true" ] && [ "${BITRISE_GIT_EVENT_TYPE:-}" = "pull_request" ]; then
npx rock remote-cache delete --name "$ARTIFACT_NAME" --all-but-latest --json
fi


# Cleanup Cache glue
rm -rf .rock/cache/project.json

# Outputs
[ -n "${ARTIFACT_URL:-}" ] && envman add --key ARTIFACT_URL --value "$ARTIFACT_URL"
[ -n "${ARTIFACT_ID:-}" ] && envman add --key ARTIFACT_ID --value "$ARTIFACT_ID"

log "Done."
Loading