diff --git a/BITRISE.md b/BITRISE.md new file mode 100644 index 0000000..ff678a6 --- /dev/null +++ b/BITRISE.md @@ -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 diff --git a/README.md b/README.md index e7ed31e..6ebb929 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -44,7 +47,7 @@ jobs: # ad-hoc: true ``` -## Inputs +## GitHub Actions Inputs | Input | Description | Required | Default | | ----------------------------- | ------------------------------------------------------------------------------- | -------- | ----------- | @@ -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 | | -------------- | ------------------------- | diff --git a/bitrise.yml b/bitrise.yml new file mode 100644 index 0000000..4bcf1bc --- /dev/null +++ b/bitrise.yml @@ -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:-}" + echo "ARTIFACT_ID=${ARTIFACT_ID:-}" diff --git a/step.sh b/step.sh new file mode 100755 index 0000000..a4de7fc --- /dev/null +++ b/step.sh @@ -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:-}" + 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." diff --git a/step.yml b/step.yml new file mode 100644 index 0000000..60e63a3 --- /dev/null +++ b/step.yml @@ -0,0 +1,88 @@ +title: Rock Remote Build - iOS +summary: Build iOS apps using Rock. +description: | + This step builds iOS apps with Rock, optionally re-signing IPAs + for PRs or device builds. Code signing must be configured separately + in your workflow before using this step. +website: https://github.com/callstackincubator/ios +source_code_url: https://github.com/callstackincubator/ios +support_url: https://github.com/callstackincubator/ios/issues + +project_type_tags: + - ios + - react-native + +type_tags: + - build + +toolkit: + bash: + entry_file: step.sh + +deps: + brew: + - name: jq + +inputs: + - WORKING_DIRECTORY: + opts: + title: Working directory for the build + summary: Path where the build command should be run + description: Can be relative or absolute. Defaults to "." + is_required: false + is_expand: true + value: "." + + - DESTINATION: + opts: + title: Destination for the build + summary: "Use 'simulator' or 'device'. Code signing must be configured separately." + is_required: true + value: "simulator" + + - SCHEME: + opts: + title: Xcode scheme + is_required: true + value: "" + + - CONFIGURATION: + opts: + title: Xcode configuration + is_required: true + value: "" + + - RE_SIGN: + opts: + title: Re-sign IPA with new JS bundle + summary: Requires certificates in keychain. No signing for simulator builds. + is_required: false + value: "" + + - AD_HOC: + opts: + title: Upload IPA for ad-hoc distribution + is_required: false + value: "false" + + - SIGNING_IDENTITY: + opts: + title: Code signing identity for re-signing + summary: Optional. Auto-detects available certificates if not provided. + is_required: false + value: "" + + - ROCK_BUILD_EXTRA_PARAMS: + opts: + title: Extra parameters to pass to "rock build:ios" + is_required: false + value: "" + +outputs: + - ARTIFACT_URL: + opts: + title: URL of the built iOS artifact + + - ARTIFACT_ID: + opts: + title: ID of the built iOS artifact