#!/bin/bash## Bundle the application for distribution.## See the parameter parsing section below for available options.## Note that there are several steps to complete before the app is ready to# be distributed, one of which is asynchronous and requires apple to process# our binary.## The exact steps depend on which artifact we are building, the desktop app or the CLI. Overall,# the steps are:## 1) Create the binary or mac bundle by running `cargo`.# 2) Create a keychain based on our distribution cert.# 3) Codesign the built artifact using the keychain.## Additionally, for an app, we create a dmg:# 4) Create the dmg using `hdiutil create`.# 5) Codesign our dmg using the keychain.# 6) Upload the app to Apple for it to be notarized - this is async, and we# poll until it is done.# 7) "Staple" the notarization to the dmg.## Once the stapling is done, the app can be shared.## Three passwords are read from GCP Secret Manager (or from env if --read-passwords-from-env is set) if you are codesigning.# 1) WARP_NOTARIZATION_PASSWORD: This is an "app-specific password" that# is tied to the zach@warp.dev account. See https://support.apple.com/en-us/HT204397# 2) WARP_DEVELOPER_ID_CERT_PASSWORD: This is a password tied to the private key# of our cert - it is needed to use the cert to sign our binary.# 3) WARP_CODESIGN_KEYCHAIN_PASSWORD: This is an arbitrary password only used# in the lifetime of this app for creating the keychain used to sign. Can# be anything.## See# https://github.com/burtonageo/cargo-bundle# https://wiki.lazarus.freepascal.org/Code_Signing_for_macOS# https://wiki.lazarus.freepascal.org/Notarization_for_macOS_10.14.5%2B# https://developer.apple.com/developer-id/# https://github.com/atom/atom/blob/976cb9ef3a611163052f9d31c6c3685dc1e6c5b4/script/lib/code-sign-on-mac.js set -e # Determine the repository root directoryWORKSPACE_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # This is used to set the minimum deployment target for mac# https://cmake.org/cmake/help/latest/envvar/MACOSX_DEPLOYMENT_TARGET.html# If unset, it uses the system one, but we want to build for older versions# of mac os by default.## This should be kept in sync with the definition of this variable in# `.cargo/config.toml`.export MACOSX_DEPLOYMENT_TARGET="10.14" # Constants used later in the script.INTEL_ARCH="x86_64"INTEL_TARGET="$INTEL_ARCH-apple-darwin"ARM_ARCH="aarch64"ARM_TARGET="$ARM_ARCH-apple-darwin"DEFAULT_ARCH="$(rustc --print cfg | grep target_arch)" # Function to clean up temporary DMG filescleanup_dmg_files() { local target_dir="$1" if [ -d "$target_dir" ]; then echo "Cleaning up temporary DMG files in $target_dir" find "$target_dir" -name "*.dmg" -type f -delete find "$target_dir" -name "rw.*.dmg" -type f -delete fi # Also check if any volumes are mounted and unmount them hdiutil info | grep "/Volumes/Warp.*" | awk '{print $1}' | while read -r disk; do echo "Unmounting disk image: $disk" hdiutil detach "$disk" -force || true done} # Clean up the temporary codesigning keychain.cleanup_codesign_keychain() { if [[ "${CODESIGN:-false}" = true && -n "${CODESIGN_KEYCHAIN_NAME:-}" ]]; then echo "Cleaning up by deleting $CODESIGN_KEYCHAIN_NAME keychain." security delete-keychain "$CODESIGN_KEYCHAIN_NAME" || echo "No keychain to delete or already deleted." fi} # Set up cleanup traptrap 'cleanup_dmg_files "$DMG_DIR"; cleanup_codesign_keychain' EXIT # Define a helper function that uses Python to compute a relative path.function relpath() { python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";} # Retry timestamped signing because Apple's timestamp service can fail# transiently. All call sites use force signing so retries are idempotent.function codesign_with_retry() { local max_attempts="${CODESIGN_MAX_ATTEMPTS:-4}" local initial_retry_delay_seconds="${CODESIGN_INITIAL_RETRY_DELAY_SECONDS:-5}" local retry_delay_seconds="$initial_retry_delay_seconds" local attempt local status for ((attempt = 1; attempt <= max_attempts; attempt++)); do if codesign "$@"; then return 0 else status=$? fi if (( attempt == max_attempts )); then echo "Codesigning failed after $attempt attempts." return "$status" fi echo "Codesigning failed; retrying in $retry_delay_seconds seconds ($attempt/$max_attempts)." sleep "$retry_delay_seconds" retry_delay_seconds=$((retry_delay_seconds * 2)) done} # Submit once so a transient polling failure can resume the same notarization# instead of uploading a duplicate artifact.function notarize_artifact() { local artifact="$1" local auth_args=( --apple-id "$WARP_NOTARIZATION_APPLE_ID" --password "$WARP_NOTARIZATION_PASSWORD" --team-id "$APPLE_TEAM_ID" ) local submission_output if ! submission_output="$( xcrun notarytool submit "$artifact" "${auth_args[@]}" --no-wait --output-format json )"; then echo "Failed to submit $artifact for notarization." return 1 fi local submission_id if ! submission_id="$(printf '%s' "$submission_output" | plutil -extract id raw -o - -)"; then echo "Failed to read the notarization submission ID." return 1 fi if [[ -z "$submission_id" ]]; then echo "Apple did not return a notarization submission ID." return 1 fi echo "Notarization submission ID: $submission_id" local poll_interval_seconds="${NOTARIZATION_POLL_INTERVAL_SECONDS:-15}" local max_poll_failures="${NOTARIZATION_MAX_POLL_FAILURES:-5}" local initial_retry_delay_seconds="${NOTARIZATION_INITIAL_RETRY_DELAY_SECONDS:-5}" local timeout_seconds="${NOTARIZATION_TIMEOUT_SECONDS:-900}" local retry_delay_seconds="$initial_retry_delay_seconds" local consecutive_poll_failures=0 local polling_started_at="$SECONDS" while true; do if (( SECONDS - polling_started_at >= timeout_seconds )); then echo "Timed out waiting for notarization submission $submission_id after $timeout_seconds seconds." return 1 fi local info_output if info_output="$( xcrun notarytool info "$submission_id" "${auth_args[@]}" --output-format json )"; then consecutive_poll_failures=0 retry_delay_seconds="$initial_retry_delay_seconds" local status if ! status="$(printf '%s' "$info_output" | plutil -extract status raw -o - -)"; then echo "Failed to read the status of notarization submission $submission_id." return 1 fi echo "Current notarization status: $status" case "$status" in Accepted) return 0 ;; Invalid|Rejected) echo "Notarization submission $submission_id was rejected by Apple." xcrun notarytool log "$submission_id" "${auth_args[@]}" || true return 1 ;; "In Progress") sleep "$poll_interval_seconds" ;; *) echo "Unexpected notarization status for submission $submission_id: $status" return 1 ;; esac else consecutive_poll_failures=$((consecutive_poll_failures + 1)) if (( consecutive_poll_failures >= max_poll_failures )); then echo "Failed to poll notarization submission $submission_id after $consecutive_poll_failures consecutive attempts." return 1 fi echo "Failed to poll notarization submission $submission_id; retrying in $retry_delay_seconds seconds ($consecutive_poll_failures/$max_poll_failures)." sleep "$retry_delay_seconds" retry_delay_seconds=$((retry_delay_seconds * 2)) fi done} # Defaults for command-line flags.UNIVERSAL_BINARY=trueBUILD_BINARY=trueTARGET_ARCH=""DMG_NAME_SUFFIX=""# By default we build dev bundles.RELEASE_CHANNEL="dev"FEATURES="release_bundle,cocoa_sentry,extern_plist"REGISTER_SERVICES=trueDEBUG=falseARTIFACT="app"CODESIGN=trueSELFSIGN=falseOPEN_AFTER_BUNDLE=falseREAD_PASSWORDS_FROM_ENV=false PARAMS=""while (( "$#" )); do case "$1" in # Speed up compilation time by only compiling a debug version of the app, # rather than a release version --debug) DEBUG=true shift ;; # Only run `cargo check` without producing a bundle --check-only) echo 'Only running `cargo check` and not producing a bundle.' CHECK_ONLY="true" shift ;; # Skip building the binary (assume it's already built) --skip-build) echo "Skipping binary build step." BUILD_BINARY=false shift ;; # Skip code signing process --nosign) echo "Skipping code signing." CODESIGN=false SELFSIGN=false shift ;; # Sign with a local Apple Development cert instead of the official Warp cert. # Useful for local debug builds when you don't have access to the company signing key. # Falls back to ad-hoc signing if no Apple Development cert is found. --selfsign) echo "Self-signing enabled." CODESIGN=false SELFSIGN=true shift ;; # Build only for the default target architecture instead of universal binary --nouniversal) echo "Only building for default target $DEFAULT_TARGET, not a universal binary." UNIVERSAL_BINARY=false shift ;; # Build only for a specific architecture (implies --nouniversal) --arch) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then if [ "$2" = "x86_64" -o "$2" = "aarch64" ]; then echo "Building for specific architecture: $2" TARGET_ARCH=$2 UNIVERSAL_BINARY=false shift 2 else echo "Error: --arch must be either x86_64 or aarch64, got '$2'" >&2 exit 1 fi else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Set a custom suffix for the DMG file --dmg-name-suffix) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then echo "Setting custom DMG name suffix to $2" DMG_NAME_SUFFIX=$2 shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Open the parent directory after bundling completes. -o|--open) OPEN_AFTER_BUNDLE=true shift ;; # Specify the release channel (local, dev, preview, stable, oss) -c|--channel) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then RELEASE_CHANNEL=$2 shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Set a specific Git release tag for the build --release-tag) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then echo "Setting release tag to $2" export GIT_RELEASE_TAG=$2 shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Read codesigning passwords from environment variables instead of GCP secret manager --read-passwords-from-env) echo "Reading codesigning passwords from env." READ_PASSWORDS_FROM_ENV=true shift ;; --features) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then echo "Adding Cargo features: $2" FEATURES="$FEATURES,$2" shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; --artifact) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then if [[ "$2" != "app" && "$2" != "cli" && "$2" != "warpctrl" && "$2" != "tui" ]]; then echo "Error: --artifact must be 'app', 'cli', 'warpctrl', or 'tui', got '$2'" >&2 exit 1 fi ARTIFACT="$2" shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; *) # preserve positional arguments PARAMS="$PARAMS $1" shift ;; esacdone# set positional arguments in their proper placeeval set -- "$PARAMS" # Infer a cargo profile from the bundle configuration.if [[ $DEBUG = true ]]; then CARGO_PROFILE="dev"elif [[ $RELEASE_CHANNEL = "local" || $RELEASE_CHANNEL = "dev" ]]; then # For dev bundles, we want to enable debug assertions to # catch violations that would otherwise silently pass in # a normal release build (e.g. in stable). if [[ "$ARTIFACT" == "tui" ]]; then CARGO_PROFILE="release-tui-debug-assertions" elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then CARGO_PROFILE="release-cli-debug_assertions" else CARGO_PROFILE="release-lto-debug_assertions" fielse if [[ "$ARTIFACT" == "tui" ]]; then CARGO_PROFILE="release-tui" elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then CARGO_PROFILE="release-cli" else CARGO_PROFILE="release-lto" fifi TARGET_PROFILE_DIR="$CARGO_PROFILE"if [[ "$CARGO_PROFILE" == "dev" ]]; then TARGET_PROFILE_DIR="debug"fi if [[ $RELEASE_CHANNEL = "local" ]]; then WARP_BIN="warp" BUNDLE_ID="dev.warp.Warp-Local" WARP_APP_NAME="WarpLocal" WARP_SCHEME_NAME="warplocal" FEATURES="$FEATURES,agent_mode_debug" # For local builds, use different versions of our bundled frameworks (e.g.: # Sentry). This needs to be exported so it can be referenced by # app/build.rs later, while running `cargo bundle`. export FRAMEWORK_OVERRIDE="dev"elif [[ $RELEASE_CHANNEL = "dev" ]]; then WARP_BIN="dev" BUNDLE_ID="dev.warp.Warp-Dev" WARP_APP_NAME="WarpDev" WARP_SCHEME_NAME="warpdev" FEATURES="$FEATURES,agent_mode_debug" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking" # Enable the Warp Control CLI wrapper for local scripting/automation. FEATURES="$FEATURES,warp_control_cli" # For dev builds, use different versions of our bundled frameworks (e.g.: # Sentry). This needs to be exported so it can be referenced by # app/build.rs later, while running `cargo bundle`. export FRAMEWORK_OVERRIDE="dev" export HANDLE_MARKDOWN=1elif [[ $RELEASE_CHANNEL = "preview" ]]; then WARP_BIN="preview" BUNDLE_ID="dev.warp.Warp-Preview" WARP_APP_NAME="WarpPreview" WARP_SCHEME_NAME="warppreview" FEATURES="$FEATURES,preview_channel" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking"elif [[ $RELEASE_CHANNEL = "stable" ]]; then WARP_BIN="stable" BUNDLE_ID="dev.warp.Warp-Stable" WARP_APP_NAME="Warp" WARP_SCHEME_NAME="warp" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking"elif [[ $RELEASE_CHANNEL = "oss" ]]; then WARP_BIN="warp-oss" BUNDLE_ID="dev.warp.WarpOss" WARP_APP_NAME="WarpOss" WARP_SCHEME_NAME="warposs" # The OSS channel does not ship Sentry, so drop the cocoa_sentry feature # (which would otherwise pull in the Sentry framework dependency). FEATURES="release_bundle,extern_plist"fi OUT_DIR="target/$TARGET_PROFILE_DIR/bundle/osx"DOCK_TILE_PLUGIN_DIR="target/$TARGET_PROFILE_DIR/WarpDockTilePlugin.docktileplugin" # Handle specific architecture targetingif [[ -n "$TARGET_ARCH" ]]; then # Building for a specific architecture if [[ "$TARGET_ARCH" == "$INTEL_ARCH" ]]; then echo "Building specifically for $INTEL_TARGET" DEFAULT_TARGET="$INTEL_TARGET" BUNDLE_DIR="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/bundle/osx" elif [[ "$TARGET_ARCH" == "$ARM_ARCH" ]]; then echo "Building specifically for $ARM_TARGET" DEFAULT_TARGET="$ARM_TARGET" BUNDLE_DIR="target/$ARM_TARGET/$TARGET_PROFILE_DIR/bundle/osx" fielse # Auto-detect default target based on current architecture if [[ "$DEFAULT_ARCH" == *"$INTEL_ARCH"* ]]; then echo "Default target is $INTEL_TARGET" DEFAULT_TARGET="$INTEL_TARGET" ADDITIONAL_TARGET="$ARM_TARGET" BUNDLE_DIR="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/bundle/osx" elif [[ "$DEFAULT_ARCH" == *"$ARM_ARCH"* ]]; then echo "Default target is $ARM_TARGET" DEFAULT_TARGET="$ARM_TARGET" ADDITIONAL_TARGET="$INTEL_TARGET" BUNDLE_DIR="target/$ARM_TARGET/$TARGET_PROFILE_DIR/bundle/osx" fifi # Apple Silicon hosts transparently execute Intel binaries via Rosetta 2, but# Intel hosts cannot execute Apple Silicon binaries, so only that direction is# unsafe. Resolve this once, ahead of the artifact-specific configuration# below (which determines UNIVERSAL_BINARY's final value), so the# settings-schema defaults further down can fail clearly instead of hitting# an exec format error.## `uname -m` reports the architecture of the running process, not the# hardware: inside a Rosetta-translated x86_64 shell on Apple Silicon it# reports x86_64 even though the hardware can execute arm64 binaries# directly. `hw.optional.arm64` reflects the actual hardware instead, so# check it first.if [[ "$(sysctl -n hw.optional.arm64 2>/dev/null)" == "1" ]]; then HOST_ARCH="aarch64"else case "$(uname -m)" in arm64|aarch64) HOST_ARCH="aarch64" ;; x86_64) HOST_ARCH="x86_64" ;; *) echo "Error: Unsupported host architecture: $(uname -m)" >&2 exit 1 ;; esacfi # Set artifact-specific configuration.if [[ "$ARTIFACT" == cli || "$ARTIFACT" == warpctrl ]]; then UNIVERSAL_BINARY=false OPEN_AFTER_BUNDLE=false FEATURES="$FEATURES,standalone"elif [[ "$ARTIFACT" == tui ]]; thenLines 1–500 of 1102. Load more
#!/bin/bash # # Bundle the application for distribution. # # See the parameter parsing section below for available options. # # Note that there are several steps to complete before the app is ready to # be distributed, one of which is asynchronous and requires apple to process # our binary. # # The exact steps depend on which artifact we are building, the desktop app or the CLI. Overall, # the steps are: # # 1) Create the binary or mac bundle by running `cargo`. # 2) Create a keychain based on our distribution cert. # 3) Codesign the built artifact using the keychain. # # Additionally, for an app, we create a dmg: # 4) Create the dmg using `hdiutil create`. # 5) Codesign our dmg using the keychain. # 6) Upload the app to Apple for it to be notarized - this is async, and we # poll until it is done. # 7) "Staple" the notarization to the dmg. # # Once the stapling is done, the app can be shared. # # Three passwords are read from GCP Secret Manager (or from env if --read-passwords-from-env is set) if you are codesigning. # 1) WARP_NOTARIZATION_PASSWORD: This is an "app-specific password" that # is tied to the zach@warp.dev account. See https://support.apple.com/en-us/HT204397 # 2) WARP_DEVELOPER_ID_CERT_PASSWORD: This is a password tied to the private key # of our cert - it is needed to use the cert to sign our binary. # 3) WARP_CODESIGN_KEYCHAIN_PASSWORD: This is an arbitrary password only used # in the lifetime of this app for creating the keychain used to sign. Can # be anything. # # See # https://github.com/burtonageo/cargo-bundle # https://wiki.lazarus.freepascal.org/Code_Signing_for_macOS # https://wiki.lazarus.freepascal.org/Notarization_for_macOS_10.14.5%2B # https://developer.apple.com/developer-id/ # https://github.com/atom/atom/blob/976cb9ef3a611163052f9d31c6c3685dc1e6c5b4/script/lib/code-sign-on-mac.js set -e # Determine the repository root directory WORKSPACE_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # This is used to set the minimum deployment target for mac # https://cmake.org/cmake/help/latest/envvar/MACOSX_DEPLOYMENT_TARGET.html # If unset, it uses the system one, but we want to build for older versions # of mac os by default. # # This should be kept in sync with the definition of this variable in # `.cargo/config.toml`. export MACOSX_DEPLOYMENT_TARGET="10.14" # Constants used later in the script. INTEL_ARCH="x86_64" INTEL_TARGET="$INTEL_ARCH-apple-darwin" ARM_ARCH="aarch64" ARM_TARGET="$ARM_ARCH-apple-darwin" DEFAULT_ARCH="$(rustc --print cfg | grep target_arch)" # Function to clean up temporary DMG files cleanup_dmg_files() { local target_dir="$1" if [ -d "$target_dir" ]; then echo "Cleaning up temporary DMG files in $target_dir" find "$target_dir" -name "*.dmg" -type f -delete find "$target_dir" -name "rw.*.dmg" -type f -delete fi # Also check if any volumes are mounted and unmount them hdiutil info | grep "/Volumes/Warp.*" | awk '{print $1}' | while read -r disk; do echo "Unmounting disk image: $disk" hdiutil detach "$disk" -force || true done } # Clean up the temporary codesigning keychain. cleanup_codesign_keychain() { if [[ "${CODESIGN:-false}" = true && -n "${CODESIGN_KEYCHAIN_NAME:-}" ]]; then echo "Cleaning up by deleting $CODESIGN_KEYCHAIN_NAME keychain." security delete-keychain "$CODESIGN_KEYCHAIN_NAME" || echo "No keychain to delete or already deleted." fi } # Set up cleanup trap trap 'cleanup_dmg_files "$DMG_DIR"; cleanup_codesign_keychain' EXIT # Define a helper function that uses Python to compute a relative path. function relpath() { python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@"; } # Retry timestamped signing because Apple's timestamp service can fail # transiently. All call sites use force signing so retries are idempotent. function codesign_with_retry() { local max_attempts="${CODESIGN_MAX_ATTEMPTS:-4}" local initial_retry_delay_seconds="${CODESIGN_INITIAL_RETRY_DELAY_SECONDS:-5}" local retry_delay_seconds="$initial_retry_delay_seconds" local attempt local status for ((attempt = 1; attempt <= max_attempts; attempt++)); do if codesign "$@"; then return 0 else status=$? fi if (( attempt == max_attempts )); then echo "Codesigning failed after $attempt attempts." return "$status" fi echo "Codesigning failed; retrying in $retry_delay_seconds seconds ($attempt/$max_attempts)." sleep "$retry_delay_seconds" retry_delay_seconds=$((retry_delay_seconds * 2)) done } # Submit once so a transient polling failure can resume the same notarization # instead of uploading a duplicate artifact. function notarize_artifact() { local artifact="$1" local auth_args=( --apple-id "$WARP_NOTARIZATION_APPLE_ID" --password "$WARP_NOTARIZATION_PASSWORD" --team-id "$APPLE_TEAM_ID" ) local submission_output if ! submission_output="$( xcrun notarytool submit "$artifact" "${auth_args[@]}" --no-wait --output-format json )"; then echo "Failed to submit $artifact for notarization." return 1 fi local submission_id if ! submission_id="$(printf '%s' "$submission_output" | plutil -extract id raw -o - -)"; then echo "Failed to read the notarization submission ID." return 1 fi if [[ -z "$submission_id" ]]; then echo "Apple did not return a notarization submission ID." return 1 fi echo "Notarization submission ID: $submission_id" local poll_interval_seconds="${NOTARIZATION_POLL_INTERVAL_SECONDS:-15}" local max_poll_failures="${NOTARIZATION_MAX_POLL_FAILURES:-5}" local initial_retry_delay_seconds="${NOTARIZATION_INITIAL_RETRY_DELAY_SECONDS:-5}" local timeout_seconds="${NOTARIZATION_TIMEOUT_SECONDS:-900}" local retry_delay_seconds="$initial_retry_delay_seconds" local consecutive_poll_failures=0 local polling_started_at="$SECONDS" while true; do if (( SECONDS - polling_started_at >= timeout_seconds )); then echo "Timed out waiting for notarization submission $submission_id after $timeout_seconds seconds." return 1 fi local info_output if info_output="$( xcrun notarytool info "$submission_id" "${auth_args[@]}" --output-format json )"; then consecutive_poll_failures=0 retry_delay_seconds="$initial_retry_delay_seconds" local status if ! status="$(printf '%s' "$info_output" | plutil -extract status raw -o - -)"; then echo "Failed to read the status of notarization submission $submission_id." return 1 fi echo "Current notarization status: $status" case "$status" in Accepted) return 0 ;; Invalid|Rejected) echo "Notarization submission $submission_id was rejected by Apple." xcrun notarytool log "$submission_id" "${auth_args[@]}" || true return 1 ;; "In Progress") sleep "$poll_interval_seconds" ;; *) echo "Unexpected notarization status for submission $submission_id: $status" return 1 ;; esac else consecutive_poll_failures=$((consecutive_poll_failures + 1)) if (( consecutive_poll_failures >= max_poll_failures )); then echo "Failed to poll notarization submission $submission_id after $consecutive_poll_failures consecutive attempts." return 1 fi echo "Failed to poll notarization submission $submission_id; retrying in $retry_delay_seconds seconds ($consecutive_poll_failures/$max_poll_failures)." sleep "$retry_delay_seconds" retry_delay_seconds=$((retry_delay_seconds * 2)) fi done } # Defaults for command-line flags. UNIVERSAL_BINARY=true BUILD_BINARY=true TARGET_ARCH="" DMG_NAME_SUFFIX="" # By default we build dev bundles. RELEASE_CHANNEL="dev" FEATURES="release_bundle,cocoa_sentry,extern_plist" REGISTER_SERVICES=true DEBUG=false ARTIFACT="app" CODESIGN=true SELFSIGN=false OPEN_AFTER_BUNDLE=false READ_PASSWORDS_FROM_ENV=false PARAMS="" while (( "$#" )); do case "$1" in # Speed up compilation time by only compiling a debug version of the app, # rather than a release version --debug) DEBUG=true shift ;; # Only run `cargo check` without producing a bundle --check-only) echo 'Only running `cargo check` and not producing a bundle.' CHECK_ONLY="true" shift ;; # Skip building the binary (assume it's already built) --skip-build) echo "Skipping binary build step." BUILD_BINARY=false shift ;; # Skip code signing process --nosign) echo "Skipping code signing." CODESIGN=false SELFSIGN=false shift ;; # Sign with a local Apple Development cert instead of the official Warp cert. # Useful for local debug builds when you don't have access to the company signing key. # Falls back to ad-hoc signing if no Apple Development cert is found. --selfsign) echo "Self-signing enabled." CODESIGN=false SELFSIGN=true shift ;; # Build only for the default target architecture instead of universal binary --nouniversal) echo "Only building for default target $DEFAULT_TARGET, not a universal binary." UNIVERSAL_BINARY=false shift ;; # Build only for a specific architecture (implies --nouniversal) --arch) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then if [ "$2" = "x86_64" -o "$2" = "aarch64" ]; then echo "Building for specific architecture: $2" TARGET_ARCH=$2 UNIVERSAL_BINARY=false shift 2 else echo "Error: --arch must be either x86_64 or aarch64, got '$2'" >&2 exit 1 fi else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Set a custom suffix for the DMG file --dmg-name-suffix) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then echo "Setting custom DMG name suffix to $2" DMG_NAME_SUFFIX=$2 shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Open the parent directory after bundling completes. -o|--open) OPEN_AFTER_BUNDLE=true shift ;; # Specify the release channel (local, dev, preview, stable, oss) -c|--channel) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then RELEASE_CHANNEL=$2 shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Set a specific Git release tag for the build --release-tag) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then echo "Setting release tag to $2" export GIT_RELEASE_TAG=$2 shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; # Read codesigning passwords from environment variables instead of GCP secret manager --read-passwords-from-env) echo "Reading codesigning passwords from env." READ_PASSWORDS_FROM_ENV=true shift ;; --features) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then echo "Adding Cargo features: $2" FEATURES="$FEATURES,$2" shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; --artifact) if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then if [[ "$2" != "app" && "$2" != "cli" && "$2" != "warpctrl" && "$2" != "tui" ]]; then echo "Error: --artifact must be 'app', 'cli', 'warpctrl', or 'tui', got '$2'" >&2 exit 1 fi ARTIFACT="$2" shift 2 else echo "Error: Argument for $1 is missing" >&2 exit 1 fi ;; *) # preserve positional arguments PARAMS="$PARAMS $1" shift ;; esac done # set positional arguments in their proper place eval set -- "$PARAMS" # Infer a cargo profile from the bundle configuration. if [[ $DEBUG = true ]]; then CARGO_PROFILE="dev" elif [[ $RELEASE_CHANNEL = "local" || $RELEASE_CHANNEL = "dev" ]]; then # For dev bundles, we want to enable debug assertions to # catch violations that would otherwise silently pass in # a normal release build (e.g. in stable). if [[ "$ARTIFACT" == "tui" ]]; then CARGO_PROFILE="release-tui-debug-assertions" elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then CARGO_PROFILE="release-cli-debug_assertions" else CARGO_PROFILE="release-lto-debug_assertions" fi else if [[ "$ARTIFACT" == "tui" ]]; then CARGO_PROFILE="release-tui" elif [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then CARGO_PROFILE="release-cli" else CARGO_PROFILE="release-lto" fi fi TARGET_PROFILE_DIR="$CARGO_PROFILE" if [[ "$CARGO_PROFILE" == "dev" ]]; then TARGET_PROFILE_DIR="debug" fi if [[ $RELEASE_CHANNEL = "local" ]]; then WARP_BIN="warp" BUNDLE_ID="dev.warp.Warp-Local" WARP_APP_NAME="WarpLocal" WARP_SCHEME_NAME="warplocal" FEATURES="$FEATURES,agent_mode_debug" # For local builds, use different versions of our bundled frameworks (e.g.: # Sentry). This needs to be exported so it can be referenced by # app/build.rs later, while running `cargo bundle`. export FRAMEWORK_OVERRIDE="dev" elif [[ $RELEASE_CHANNEL = "dev" ]]; then WARP_BIN="dev" BUNDLE_ID="dev.warp.Warp-Dev" WARP_APP_NAME="WarpDev" WARP_SCHEME_NAME="warpdev" FEATURES="$FEATURES,agent_mode_debug" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking" # Enable the Warp Control CLI wrapper for local scripting/automation. FEATURES="$FEATURES,warp_control_cli" # For dev builds, use different versions of our bundled frameworks (e.g.: # Sentry). This needs to be exported so it can be referenced by # app/build.rs later, while running `cargo bundle`. export FRAMEWORK_OVERRIDE="dev" export HANDLE_MARKDOWN=1 elif [[ $RELEASE_CHANNEL = "preview" ]]; then WARP_BIN="preview" BUNDLE_ID="dev.warp.Warp-Preview" WARP_APP_NAME="WarpPreview" WARP_SCHEME_NAME="warppreview" FEATURES="$FEATURES,preview_channel" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking" elif [[ $RELEASE_CHANNEL = "stable" ]]; then WARP_BIN="stable" BUNDLE_ID="dev.warp.Warp-Stable" WARP_APP_NAME="Warp" WARP_SCHEME_NAME="warp" # Enable heap usage tracking & profiling using jemalloc through pprof. FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking" elif [[ $RELEASE_CHANNEL = "oss" ]]; then WARP_BIN="warp-oss" BUNDLE_ID="dev.warp.WarpOss" WARP_APP_NAME="WarpOss" WARP_SCHEME_NAME="warposs" # The OSS channel does not ship Sentry, so drop the cocoa_sentry feature # (which would otherwise pull in the Sentry framework dependency). FEATURES="release_bundle,extern_plist" fi OUT_DIR="target/$TARGET_PROFILE_DIR/bundle/osx" DOCK_TILE_PLUGIN_DIR="target/$TARGET_PROFILE_DIR/WarpDockTilePlugin.docktileplugin" # Handle specific architecture targeting if [[ -n "$TARGET_ARCH" ]]; then # Building for a specific architecture if [[ "$TARGET_ARCH" == "$INTEL_ARCH" ]]; then echo "Building specifically for $INTEL_TARGET" DEFAULT_TARGET="$INTEL_TARGET" BUNDLE_DIR="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/bundle/osx" elif [[ "$TARGET_ARCH" == "$ARM_ARCH" ]]; then echo "Building specifically for $ARM_TARGET" DEFAULT_TARGET="$ARM_TARGET" BUNDLE_DIR="target/$ARM_TARGET/$TARGET_PROFILE_DIR/bundle/osx" fi else # Auto-detect default target based on current architecture if [[ "$DEFAULT_ARCH" == *"$INTEL_ARCH"* ]]; then echo "Default target is $INTEL_TARGET" DEFAULT_TARGET="$INTEL_TARGET" ADDITIONAL_TARGET="$ARM_TARGET" BUNDLE_DIR="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/bundle/osx" elif [[ "$DEFAULT_ARCH" == *"$ARM_ARCH"* ]]; then echo "Default target is $ARM_TARGET" DEFAULT_TARGET="$ARM_TARGET" ADDITIONAL_TARGET="$INTEL_TARGET" BUNDLE_DIR="target/$ARM_TARGET/$TARGET_PROFILE_DIR/bundle/osx" fi fi # Apple Silicon hosts transparently execute Intel binaries via Rosetta 2, but # Intel hosts cannot execute Apple Silicon binaries, so only that direction is # unsafe. Resolve this once, ahead of the artifact-specific configuration # below (which determines UNIVERSAL_BINARY's final value), so the # settings-schema defaults further down can fail clearly instead of hitting # an exec format error. # # `uname -m` reports the architecture of the running process, not the # hardware: inside a Rosetta-translated x86_64 shell on Apple Silicon it # reports x86_64 even though the hardware can execute arm64 binaries # directly. `hw.optional.arm64` reflects the actual hardware instead, so # check it first. if [[ "$(sysctl -n hw.optional.arm64 2>/dev/null)" == "1" ]]; then HOST_ARCH="aarch64" else case "$(uname -m)" in arm64|aarch64) HOST_ARCH="aarch64" ;; x86_64) HOST_ARCH="x86_64" ;; *) echo "Error: Unsupported host architecture: $(uname -m)" >&2 exit 1 ;; esac fi # Set artifact-specific configuration. if [[ "$ARTIFACT" == cli || "$ARTIFACT" == warpctrl ]]; then UNIVERSAL_BINARY=false OPEN_AFTER_BUNDLE=false FEATURES="$FEATURES,standalone" elif [[ "$ARTIFACT" == tui ]]; then