Files
warp/script/macos/bundle
Evelyn Xu 48ac96fa20 [Quality-543]Remove check_if_token_has_shell_syntax and pin threshold to 1 for nld_heuristic_v2 (#10846)
## Description
<!-- Please remember to add your design buddy onto the PR for review, if
it contains any UI changes! -->
Implementation of improving heuristics: by allocating more traffic to
nld new classifier;
This should resolve misfires as shell for file path or url 
## Linked Issue
<!--
Link the GitHub issue this PR addresses. Before opening this PR, please
confirm:
-->
- [ ] The linked issue is labeled `ready-to-spec` or
`ready-to-implement`.
- [ ] Where appropriate, screenshots or a short video of the
implementation are included below (especially for user-visible or UI
changes).

## Testing
<!--
How did you test this change? What automated tests did you add? If you
didn't add any new tests, what's your justification for not adding any?

Manual testing is required for changes that can be manually tested, and
almost all changes can be manually tested. If your change can be
manually tested, please include screenshots or a screen recording that
show it working end to end.

You can run the app locally using `./script/run` - see WARP.md for more
details on how to get set up.
-->
`RUST_LOG=debug ./script/run --features nld_heuristic_v2`
`RUST_LOG=debug ./script/run --features nld_classifier_v2,
nld_heuristic_v2`

- [x] I have manually tested my changes locally with `./script/run`

### Screenshots / Videos
<!-- Attach screenshots or a short video demonstrating the change, where
appropriate. Remove this section if it is not relevant to your PR. -->
Test 543 misfire `read this
https://trilogy-eng.atlassian.net/browse/epmlive-17588`
- it should be true/shell in v1 and false/non-shell in v2
- with `nld_heuristic_v2` and `nld_classifier_v2`, we could see now
classifier could classify this one to prompt
<img width="2169" height="336" alt="image"
src="https://github.com/user-attachments/assets/d6129bc0-dba6-4ecd-9aab-8f36c64f63c8"
/>

<img width="2158" height="543" alt="image"
src="https://github.com/user-attachments/assets/fc698d9d-9cea-4be1-874f-32f881c6fe02"
/>

<img width="1315" height="187" alt="image"
src="https://github.com/user-attachments/assets/ebc989de-f344-4ddb-82ae-a145f55766aa"
/>
<img width="2168" height="350" alt="image"
src="https://github.com/user-attachments/assets/578eccb0-949d-4a1c-a394-ef95711c7af0"
/>



## Agent Mode
- [ ] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

<!--
## Changelog Entries for Stable

The entries below will be used when constructing a soft-copy of the
stable release changelog. Leave blank or remove the lines if no entry in
the stable changelog is needed. Entries should be on the same line,
without the `{{` `}}` brackets. You can use multiple lines, even of the
same type. The valid suffixes are:

- NEW-FEATURE: for new, relatively sizable features. Features listed
here will likely have docs / social media posts / marketing launches
associated with them, so use sparingly.
- IMPROVEMENT: for new functionality of existing features.
- BUG-FIX: for fixes related to known bugs or regressions.
- IMAGE: the image specified by the URL (hosted on GCP) will be added to
Dev & Preview releases. For Stable releases, see the pinned doc in the
#release Slack channel.
- OZ: Oz-related updates. Use `CHANGELOG-OZ`. At most 4 Oz updates are
shown in-app per release.
- NONE: Explicitly opt out of changelog inclusion. Use `CHANGELOG-NONE`
for PRs that should never appear in the changelog (e.g. refactors,
internal tooling, CI changes). This prevents the changelog agent from
inferring an entry.

CHANGELOG-NEW-FEATURE: {{text goes here...}}
CHANGELOG-IMPROVEMENT: {{text goes here...}}
CHANGELOG-BUG-FIX: {{text goes here...}}
CHANGELOG-BUG-FIX: {{more text goes here...}}
CHANGELOG-IMAGE: {{GCP-hosted URL goes here...}}
CHANGELOG-OZ: {{text goes here...}}
CHANGELOG-NONE
-->
2026-05-14 20:18:49 -07:00

850 lines
32 KiB
Bash
Executable File

#!/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:])))" "$@";
}
# 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" ]]; then
echo "Error: --artifact must be either 'app' or 'cli', 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" == "cli" ]]; then
CARGO_PROFILE="release-cli-debug_assertions"
else
CARGO_PROFILE="release-lto-debug_assertions"
fi
else
if [[ "$ARTIFACT" == "cli" ]]; 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"
# 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
# Set artifact-specific configuration.
if [[ "$ARTIFACT" == cli ]]; then
UNIVERSAL_BINARY=false
OPEN_AFTER_BUNDLE=false
FEATURES="$FEATURES,standalone"
elif [[ "$ARTIFACT" == app ]]; then
FEATURES="$FEATURES,gui"
if [[ "$RELEASE_CHANNEL" == "local" || "$RELEASE_CHANNEL" == "dev" ]]; then
FEATURES="$FEATURES,nld_classifier_v2,nld_heuristic_v2"
else
FEATURES="$FEATURES,nld_classifier_v1,nld_heuristic_v1"
fi
fi
# If we're building a universal bundle for the app artifact, make sure the additional target is available.
if [[ $UNIVERSAL_BINARY = true ]]; then
rustup target add "$ADDITIONAL_TARGET"
fi
# If we only want to check that compilation will succeed, perform the checks
# then exit. We use this script to invoke `cargo check` to ensure that we are
# using the same feature flags and profile that we would be using in production.
if [[ "$CHECK_ONLY" == "true" ]]; then
cargo check --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES"
if [[ $UNIVERSAL_BINARY = true && "$ARTIFACT" != "cli" ]]; then
cargo check --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$ADDITIONAL_TARGET" --features "$FEATURES"
fi
exit 0
fi
DMG_DIR="$BUNDLE_DIR/dmg/$WARP_BIN"
DMG_NAME="$WARP_APP_NAME.dmg"
if [[ -z "$FINAL_DMG_NAME" && -n "$DMG_NAME_SUFFIX" ]]; then
FINAL_DMG_NAME="$WARP_APP_NAME-$DMG_NAME_SUFFIX.dmg"
else
FINAL_DMG_NAME="$WARP_APP_NAME.dmg"
fi
# First clean up and prep the outdir
mkdir -p "$OUT_DIR"
rm -R "$OUT_DIR/$WARP_APP_NAME.app" || echo "No old app to remove"
rm -R "$OUT_DIR/$FINAL_DMG_NAME" || echo "No old dmg to remove"
###########################
## Step 1: Build the app ##
###########################
if [[ "$ARTIFACT" == "app" ]]; then
if [[ $BUILD_BINARY != true ]]; then
echo "Skipping binary build due to --skip-build flag"
export CARGO_BUNDLE_SKIP_BUILD=1
fi
pushd app > /dev/null
echo "Building and bundling $DEFAULT_TARGET for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID with profile $CARGO_PROFILE"
cargo bundle --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES"
popd > /dev/null
echo "Adding rpath to support mac frameworks (e.g. Sentry)"
install_name_tool -add_rpath "@executable_path/../Frameworks" "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS/$WARP_BIN"
export WARP_SCHEME_NAME
export WARP_PLIST_PATH="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Info.plist"
./script/update_plist
if [[ $REGISTER_SERVICES = true ]]; then
plutil -insert NSServices -xml "
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>New $WARP_APP_NAME Tab Here</string>
</dict>
<key>NSMessage</key>
<string>openTab</string>
<key>NSRequiredContext</key>
<dict>
<key>NSTextContent</key>
<string>FilePath</string>
</dict>
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
<string>public.plain-text</string>
</array>
</dict>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>New $WARP_APP_NAME Window Here</string>
</dict>
<key>NSMessage</key>
<string>openWindow</string>
<key>NSRequiredContext</key>
<dict>
<key>NSTextContent</key>
<string>FilePath</string>
</dict>
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
<string>public.plain-text</string>
</array>
</dict>
</array>" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist
fi
# Temporary key that ChatGPT Desktop can use to determine if the latest version of Warp supports the ChatGPT integration.
# Once support has been rolled out for a sufficient amount of time we (and ChatGPT) can remove this.
plutil -insert SUPPORTS_CHAT_GPT_WORK_WITH_APPS -bool true "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist
# Add LSBackgroundOnly key and set it to false since we need a UI app
plutil -insert LSBackgroundOnly -bool false "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist
# Add SMAuthorizedClients for macOS 13+ (Ventura) to support login item functionality
plutil -insert SMAuthorizedClients -xml "<array><string>$BUNDLE_ID</string></array>" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist
if [[ $UNIVERSAL_BINARY = true ]]; then
if [[ $BUILD_BINARY = true ]]; then
echo "Building $ADDITIONAL_TARGET to include in universal binary"
pushd app > /dev/null
cargo build --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$ADDITIONAL_TARGET" --features "$FEATURES"
popd > /dev/null
else
echo "Skipping build of $ADDITIONAL_TARGET due to --skip-build flag"
fi
tmp_bundle_dir=$(mktemp -d -t ci-XXXXXXXXXX)
echo "Adding rpath to both binaries for universal executable"
install_name_tool -add_rpath "@executable_path/../Frameworks" "target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN"
install_name_tool -add_rpath "@executable_path/../Frameworks" "target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN"
echo "Building universal binary using lipo."
lipo -create -output "$tmp_bundle_dir/$WARP_BIN" \
"target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" \
"target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN"
# Compute the real absolute path to the dSYM file, factoring in symlinks.
# Starting in Rust 1.56, the generated dSYMs are symlinks to files within the target/dep directory. For example,
# a dSYM for dev may be symlinked to target/deps/dev-080cf7e291fb066d.dSYM. This means the actual debug symbols are
# would be located at dev.dSYM/Contents/Resources/DWARF/dev-080cf7e291fb066d.dSYM where dev.dSYM is symlink to
# deps/dev-080cf7e291fb066d.dSYM. To fix this, parse out the actual name of the directory that the dSYM is
# symlinked to, since this is also the name of file that contains the debug symbols within the dSYM.
INTEL_DSYM_NAME=$(basename "$(realpath target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM)" .dSYM)
INTEL_DSYM_PATH="target/$INTEL_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM/Contents/Resources/DWARF/$INTEL_DSYM_NAME"
ARM_DSYM_NAME=$(basename "$(realpath target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM)" .dSYM)
ARM_DSYM_PATH="target/$ARM_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM/Contents/Resources/DWARF/$ARM_DSYM_NAME"
if [[ -e "$INTEL_DSYM_PATH" && -e "$ARM_DSYM_PATH" ]]; then
echo "Building universal binary .dSYM using lipo and storing in $OUT_DIR/$WARP_BIN.dSYM"
# Use lipo to merge the .dSYM files into a single universal .dSYM file.
# It expects the base name for the file (with the .dSYM suffix removed).
lipo -create -output "$OUT_DIR/$WARP_BIN.dSYM" "${INTEL_DSYM_PATH}" "${ARM_DSYM_PATH}"
fi
echo "Storing result in $BUNDLE_DIR, replacing $DEFAULT_TARGET binary with fat binary."
mv "$tmp_bundle_dir/$WARP_BIN" "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/MacOS"
elif [[ -n "$TARGET_ARCH" && -e "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" ]]; then
echo "Copying .dSYM into $OUT_DIR/$WARP_BIN.dSYM"
cp -HR "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" "$OUT_DIR/"
fi
# Note that the dock tile plugin is pre-built for both arm64 and x86_64 so we don't need to run lipo on it.
echo "Creating PlugIns directory and copying pre-built DockTilePlugin..."
mkdir -p "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/PlugIns"
cp -R "$DOCK_TILE_PLUGIN_DIR" "$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/PlugIns/"
echo "Updating plist with dock tile plugin entries"
plutil -insert NSDockTilePlugIn -string "WarpDockTilePlugin.docktileplugin" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/Info.plist
plutil -insert MainAppBundleIdentifier -string "$BUNDLE_ID" "$BUNDLE_DIR"/$WARP_APP_NAME.app/Contents/PlugIns/WarpDockTilePlugin.docktileplugin/Contents/Info.plist
BUNDLED_RESOURCES_DIR="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Resources"
echo "Preparing bundled resources..."
"$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE"
"$WORKSPACE_ROOT_DIR/script/compile_icon" "$RELEASE_CHANNEL" "$BUNDLE_DIR/$WARP_APP_NAME.app"
HELPERS_DIR="$BUNDLE_DIR/$WARP_APP_NAME.app/Contents/Helpers"
if [[ ",$FEATURES," =~ ",heap_usage_tracking," ]]; then
echo "Bundling pprof..."
"$WORKSPACE_ROOT_DIR/script/prepare_bundled_pprof" "$HELPERS_DIR"
fi
# Determine CLI wrapper script path based on release channel. Each channel's
# value here must match `Channel::cli_command_name` in the Rust source.
if [[ $RELEASE_CHANNEL = "stable" ]]; then
CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/oz"
elif [[ $RELEASE_CHANNEL = "oss" ]]; then
CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/warp-oss"
else
CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/oz-$RELEASE_CHANNEL"
fi
echo "Creating Resources/bin directory and CLI wrapper script..."
mkdir -p "$BUNDLED_RESOURCES_DIR/bin"
cat > "$CLI_SCRIPT_PATH" << 'EOF'
#!/bin/bash
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec -a "$0" "$script_dir/../../MacOS/WARP_BIN_PLACEHOLDER" "$@"
EOF
# Replace the placeholder with the actual binary name
sed -i '' "s/WARP_BIN_PLACEHOLDER/$WARP_BIN/" "$CLI_SCRIPT_PATH"
# Make the script executable
chmod +x "$CLI_SCRIPT_PATH"
# Store the built artifact locations for GitHub Actions outputs.
BINARY_PATH="target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN"
DMG_PATH="$OUT_DIR/$FINAL_DMG_NAME"
elif [[ "$ARTIFACT" == "cli" ]]; then
if [[ $BUILD_BINARY == true ]]; then
# Create Info.plist before building the app, since it's embedded at build time.
# Apple's codesigning tools will detect Info.plist files in the same directory as an executable.
# This breaks the code signature, so we must use a different location for the file.
mkdir -p "$BUNDLE_DIR"
export WARP_PLIST_PATH="$BUNDLE_DIR/cli-info.plist"
cp app/assets/resources/mac/CLI-Info.plist "$WARP_PLIST_PATH"
export WARP_PLIST_NO_FILE_TYPES=true
./script/update_plist
plutil -insert CFBundleIdentifier -string "$BUNDLE_ID" "$WARP_PLIST_PATH"
plutil -insert CFBundleName -string "$WARP_BIN" "$WARP_PLIST_PATH"
plutil -insert CFBundleExecutable -string "$WARP_BIN" "$WARP_PLIST_PATH"
export "INFO_PLIST_PATH=$(realpath "$WARP_PLIST_PATH")"
pushd app > /dev/null
echo "Building $DEFAULT_TARGET for channel $RELEASE_CHANNEL with profile $CARGO_PROFILE"
cargo build --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --target "$DEFAULT_TARGET" --features "$FEATURES"
popd > /dev/null
else
echo "Skipping binary build due to --skip-build flag"
fi
echo "Copying binary into $OUT_DIR/$WARP_BIN"
cp "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN" "$OUT_DIR/$WARP_BIN"
if [[ -n "$TARGET_ARCH" && -e "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" ]]; then
echo "Copying .dSYM into $OUT_DIR/$WARP_BIN.dSYM"
cp -HR "target/$DEFAULT_TARGET/$TARGET_PROFILE_DIR/$WARP_BIN.dSYM" "$OUT_DIR/"
fi
echo "Preparing CLI resources directory"
BUNDLED_RESOURCES_DIR="$OUT_DIR/resources"
"$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE"
# Set the primary binary path to output.
BINARY_PATH="$OUT_DIR/$WARP_BIN"
else
echo "Unsupported artifact: $ARTIFACT" >&2
exit 1
fi
##########################################
## Step 2: Create code-signing keychain ##
##########################################
if [[ $READ_PASSWORDS_FROM_ENV != true ]]; then
echo "Skipping code-signing because passwords are not available in environment."
CODESIGN=false
fi
if [[ $CODESIGN = true ]]; then
# TODO - does this need to change per user? Seems like it's tied to the WARP_NOTARIZATION_PASSWORD password.
APPLE_TEAM_ID="2BBY89MBSN"
CODESIGN_KEYCHAIN_NAME="warp-codesign-keychain"
echo "Starting codesigning..."
if [[ $READ_PASSWORDS_FROM_ENV = true ]]; then
if [ -z "$WARP_NOTARIZATION_PASSWORD" ] ; then
echo "WARP_NOTARIZATION_PASSWORD must be set for code signing"
exit 1
fi
if [ -z "$WARP_DEVELOPER_ID_CERT_PASSWORD" ] ; then
echo "WARP_DEVELOPER_ID_CERT_PASSWORD must be set for code signing"
exit 1
fi
if [ -z "$WARP_CODESIGN_KEYCHAIN_PASSWORD" ] ; then
echo "WARP_CODESIGN_KEYCHAIN_PASSWORD must be set for code signing"
exit 1
fi
fi
security delete-keychain $CODESIGN_KEYCHAIN_NAME || echo "No existing keychain to clean up".
echo "Creating $CODESIGN_KEYCHAIN_NAME keychain."
security create-keychain -p "$WARP_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME
security list-keychains -s $CODESIGN_KEYCHAIN_NAME
security set-keychain-settings -t 3600 -u $CODESIGN_KEYCHAIN_NAME
echo "Unlocking keychain and setting cert."
security unlock-keychain -p "$WARP_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME
security import <(echo "$WARP_DEVELOPER_ID_CERT" | base64 -d) -f pkcs12 -P "$WARP_DEVELOPER_ID_CERT_PASSWORD" -k $CODESIGN_KEYCHAIN_NAME -T /usr/bin/codesign
security set-key-partition-list -S "apple-tool:,apple:" -s -k "$WARP_CODESIGN_KEYCHAIN_PASSWORD" $CODESIGN_KEYCHAIN_NAME
fi
##############################
## Step 3: Codesign the app ##
##############################
if [[ $SELFSIGN = true ]]; then
SIGNING_CERT="$(security find-identity -p codesigning -v | grep "Apple Development" | awk '{print $2}' | head -1)"
if [[ -z "$SIGNING_CERT" ]]; then
echo "No Apple Development cert found, falling back to ad-hoc signing."
SIGNING_CERT="-"
else
echo "Found Apple Development certificate"
fi
if [[ "$ARTIFACT" == app ]]; then
echo "Self-signing $BUNDLE_DIR/$WARP_APP_NAME.app with ${SIGNING_CERT}..."
codesign --force --deep --options runtime --sign "$SIGNING_CERT" "$BUNDLE_DIR/$WARP_APP_NAME.app" --entitlements script/Debug-Entitlements.plist
elif [[ "$ARTIFACT" == cli ]]; then
echo "Self-signing $OUT_DIR/$WARP_BIN with ${SIGNING_CERT}..."
codesign --force --options runtime --sign "$SIGNING_CERT" "$OUT_DIR/$WARP_BIN" --entitlements script/Debug-Entitlements.plist
fi
elif [[ $CODESIGN = true ]]; then
if [[ "$ARTIFACT" == app ]]; then
echo "Codesigning $BUNDLE_DIR/$WARP_APP_NAME.app..."
# Use --deep so we sign bundled frameworks as well
codesign --deep -f -o runtime --timestamp -s "$APPLE_TEAM_ID" "$BUNDLE_DIR/$WARP_APP_NAME.app" --entitlements script/Entitlements.plist
elif [[ "$ARTIFACT" == cli ]]; then
echo "Codesigning $OUT_DIR/$WARP_BIN..."
codesign -f -o runtime --timestamp -s "$APPLE_TEAM_ID" "$OUT_DIR/$WARP_BIN" --entitlements script/Entitlements.plist
# Create the .zip for notarization in a separate location - otherwise, Apple's codesigning
# tools decide that it's a sealed resource that belongs to the binary and needs to also be signed.
NOTARIZATION_ARTIFACT="$BUNDLE_DIR/${WARP_BIN}_notarize.zip"
if [[ -e "$NOTARIZATION_ARTIFACT" ]]; then
echo "Removing old notarization artifact..."
rm "$NOTARIZATION_ARTIFACT"
fi
# Create a .zip archive to notarize.
ditto -c -k "$OUT_DIR/$WARP_BIN" "$NOTARIZATION_ARTIFACT"
# It's not possible to staple notarization tickets to standalone binaries:
# https://developer.apple.com/documentation/security/customizing-the-notarization-workflow?language=objc#Staple-the-ticket-to-your-distribution
STAPLE_TICKET=false
fi
fi
########################
## Step 4: Create DMG ##
########################
if [[ "$ARTIFACT" = app ]]; then
function create_warp_dmg() {
echo "Creating $DMG_DIR/$DMG_NAME..."
rm "$DMG_DIR/$DMG_NAME" || true
local source_folder="$1"
local args=(
--volname Warp
# For --no-internet-enable, see https://github.com/create-dmg/create-dmg/issues/179
--no-internet-enable
--background app/assets/resources/mac/warp_install_image.png
--icon-size 128
--window-size 700 500
--format UDZO
--app-drop-link 550 250
--icon "$WARP_APP_NAME.app" 150 250
# macOS 26.4 Beta has issues with mounting HFS+ DMGs, so we're using APFS instead.
# APFS has been supported as a DMG filesystem since macOS 10.13, and we target 10.14
# as our minimum version.
#
# See: https://developer.apple.com/documentation/macos-release-notes/macos-26_4-release-notes#External-Media
--filesystem APFS
)
# Skip running an AppleScript to format the DMG contents if running in Namespace - this consistently times out.
# See https://github.com/create-dmg/create-dmg/issues/72
if [[ "${RUNNER_NAME:-}" == nsc-* ]]; then
args+=(--skip-jenkins)
fi
args+=("$DMG_DIR/$DMG_NAME" "$source_folder")
create-dmg "${args[@]}"
}
# If we're codesigning, stage the signed app and use that to create the DMG.
# Otherwise, create a DMG from the bundle dir directly.
if [[ $CODESIGN = true ]]; then
if test -d "$DMG_DIR"; then
echo "Clearing old dmg directory $DMG_DIR"
rm -r "$DMG_DIR"
fi
echo "Creating $DMG_DIR"
mkdir -p "$DMG_DIR"
cp -R "$BUNDLE_DIR/$WARP_APP_NAME.app" "$DMG_DIR"
create_warp_dmg "$DMG_DIR"
echo "Codesigning $DMG_DIR/$DMG_NAME..."
codesign -s "$APPLE_TEAM_ID" --timestamp "$DMG_DIR/$DMG_NAME"
NOTARIZATION_ARTIFACT="$DMG_DIR/$DMG_NAME"
STAPLE_TICKET=true
else
echo "Creating $DMG_DIR"
mkdir -p "$DMG_DIR"
echo "Cleaning up any existing DMG files before creating new ones..."
cleanup_dmg_files "$DMG_DIR"
create_warp_dmg "$BUNDLE_DIR"
fi
fi
##############################
## Step 5: Notarize the app ##
##############################
if [[ $CODESIGN = true ]]; then
echo "Uploading $NOTARIZATION_ARTIFACT to Apple for notarization..."
xcrun notarytool submit "$NOTARIZATION_ARTIFACT" --apple-id "$WARP_NOTARIZATION_APPLE_ID" --password "$WARP_NOTARIZATION_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait
if [[ $STAPLE_TICKET = true ]]; then
echo "Attempting to staple the notarization ticket to $NOTARIZATION_ARTIFACT..."
xcrun stapler staple "$NOTARIZATION_ARTIFACT"
fi
if [[ $? != 0 ]]; then
echo "Notarization failed; see above output for details."
exit 1
fi
# Verify the notarization results (this is format-dependent)
echo "Verifying notarization ticket..."
if [[ "$ARTIFACT" = app ]]; then
xcrun stapler validate "$DMG_DIR/$DMG_NAME"
elif [[ "$ARTIFACT" = cli ]]; then
spctl -a -t open --context context:primary-signature -vv "$OUT_DIR/$WARP_BIN"
fi
fi
#######################################
## Step 6: Copy and output artifacts ##
#######################################
if [[ "$ARTIFACT" = app ]]; then
echo "Copying dmg and app to $OUT_DIR"
cp -R "$BUNDLE_DIR/$WARP_APP_NAME.app" "$OUT_DIR"
cp "$DMG_DIR/$DMG_NAME" "$OUT_DIR/$FINAL_DMG_NAME"
fi
# If this is being run within a GitHub action, set an output variable with the
# location of the artifacts so they can be referenced by subsequent actions.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "::echo::on"
# For individual architecture builds, output binary information
if [[ -n "$TARGET_ARCH" ]]; then
echo "binary_path=$BINARY_PATH" >> "$GITHUB_OUTPUT"
echo "target_arch=$TARGET_ARCH" >> "$GITHUB_OUTPUT"
echo "rust_target=$DEFAULT_TARGET" >> "$GITHUB_OUTPUT"
# If the dSYM is available, output both its path and its realpath.
# The realpath is needed because the dSYM is a symlink, and when preserving
# the build artifacts, we want to preserve both the symlink and its target.
DSYM_PATH="${BINARY_PATH}.dSYM"
if [[ -d "$DSYM_PATH" ]]; then
echo "dsym_path=$DSYM_PATH" >> "$GITHUB_OUTPUT"
# If the dSYM is a symlink, output its real path.
if [[ -L "$DSYM_PATH" ]]; then
echo "dsym_realpath=$(relpath $(realpath $DSYM_PATH))" >> "$GITHUB_OUTPUT"
fi
fi
fi
echo "dock_tile_plugin_dir=$DOCK_TILE_PLUGIN_DIR" >> "$GITHUB_OUTPUT"
echo "frameworks_dir=app/frameworks/${FRAMEWORK_OVERRIDE:-default}" >> "$GITHUB_OUTPUT"
# For full bundles, output DMG information
if [[ -f "$OUT_DIR/$FINAL_DMG_NAME" ]]; then
echo "dmg_name=$FINAL_DMG_NAME" >> "$GITHUB_OUTPUT"
echo "dmg_path=$DMG_PATH" >> "$GITHUB_OUTPUT"
echo "dsym_folder_path=$OUT_DIR" >> "$GITHUB_OUTPUT"
fi
if [[ -n "$BUNDLED_RESOURCES_DIR" ]]; then
echo "bundled_resources_dir=$BUNDLED_RESOURCES_DIR" >> "$GITHUB_OUTPUT"
fi
echo "::echo::off"
fi
if [[ $OPEN_AFTER_BUNDLE = true ]]; then
open "$OUT_DIR"
fi