Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Run this script once to prepare your machine to develop Warp. This script is
|
||||
# a work in progress, and may be incomplete.
|
||||
|
||||
set -e
|
||||
|
||||
if ! [ -d "/Applications/Xcode.app" ]; then
|
||||
echo "Please install Xcode from the App Store before continuing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
|
||||
# Mimic actually launching XCode, which performs some necessary set-up of the
|
||||
# development environment.
|
||||
xcodebuild -runFirstLaunch
|
||||
|
||||
if ! command -v brew; then
|
||||
echo "Installing brew..."
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
echo "Please make sure brew is set correctly in your PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v cargo; then
|
||||
echo "Installing rust..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
echo "Please start a new terminal session so that cargo is in your PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install various binaries through cargo.
|
||||
"$PWD"/script/install_cargo_test_deps
|
||||
"$PWD"/script/install_cargo_release_deps
|
||||
|
||||
# Install a version of cargo-bundle that supports the --profile flag
|
||||
cargo install cargo-bundle --git=https://github.com/burtonageo/cargo-bundle --rev ae4c76e92c08774bf54ff077b1c52e3d1cd6c16d
|
||||
|
||||
# Update brew
|
||||
brew update
|
||||
|
||||
brew install jq
|
||||
brew install getsentry/tools/sentry-cli
|
||||
brew install clang-format
|
||||
brew install create-dmg
|
||||
brew install multitime
|
||||
brew install powershell
|
||||
brew install pkgconf
|
||||
brew install llvm
|
||||
|
||||
# Install PSScriptAnalyzer for PowerShell linting
|
||||
pwsh -Command "Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser"
|
||||
|
||||
if ! [ "$(command -v docker)" ]; then
|
||||
brew install --cask docker
|
||||
fi
|
||||
|
||||
if ! [ "$(command -v gcloud)" ]; then
|
||||
brew install google-cloud-sdk
|
||||
fi
|
||||
|
||||
if [[ -z $(gcloud auth print-identity-token) ]]; then
|
||||
echo "gcloud CLI authentication missing. Press enter to continue..."
|
||||
read var
|
||||
gcloud auth login
|
||||
fi
|
||||
|
||||
# Needed for building for Mac ARM machines (e.g. M1)
|
||||
rustup target add aarch64-apple-darwin
|
||||
Executable
+823
@@ -0,0 +1,823 @@
|
||||
#!/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)
|
||||
-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).
|
||||
CARGO_PROFILE="release-lto-debug_assertions"
|
||||
else
|
||||
CARGO_PROFILE="release-lto"
|
||||
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"
|
||||
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,nld_improvements"
|
||||
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
|
||||
if [[ $RELEASE_CHANNEL = "stable" ]]; then
|
||||
CLI_SCRIPT_PATH="$BUNDLED_RESOURCES_DIR/bin/oz"
|
||||
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
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Installs macOS-specific build dependencies required to build Warp.
|
||||
|
||||
xcodebuild -downloadComponent MetalToolchain
|
||||
Executable
+142
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# macOS-specific implementation of `./script/run`. Runs a local version of
|
||||
# Warp as a real 'app' instead of a bare executable, so macOS can treat it
|
||||
# like a real signed app (custom URL schemes, user notifications, etc.).
|
||||
#
|
||||
# This script is invoked by `./script/run`, which handles cross-platform
|
||||
# setup (install_channel_config, binary name detection, feature-to-env-var
|
||||
# mapping). The following env vars are expected to be set by the caller:
|
||||
# WARP_BIN_NAME — "warp" (internal local build) or "warp-oss"
|
||||
# WARP_CHANNEL — "local" or "oss"
|
||||
# FEATURES — comma-separated cargo features (already normalized)
|
||||
# Must be called from the root directory of the warp repo.
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)"
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
: "${WARP_BIN_NAME:?WARP_BIN_NAME must be set (invoke via ./script/run)}"
|
||||
: "${WARP_CHANNEL:?WARP_CHANNEL must be set (invoke via ./script/run)}"
|
||||
: "${FEATURES:?FEATURES must be set (invoke via ./script/run)}"
|
||||
|
||||
if [ "$WARP_CHANNEL" = "local" ]; then
|
||||
WARP_APP_PATH="target/debug/bundle/osx/WarpLocal.app"
|
||||
WARP_SCHEME_NAME="warplocal"
|
||||
else
|
||||
WARP_APP_PATH="target/debug/bundle/osx/WarpOss.app"
|
||||
WARP_SCHEME_NAME="warposs"
|
||||
fi
|
||||
DONT_OPEN=false
|
||||
# Launches the binary with "open", meaning the Warp process is
|
||||
# launched by the MacOS application launcher instead of a shell session.
|
||||
# Note that since Warp isn't launched as a child process, using ctrl+c
|
||||
# won't kill the app (but will kill the tail process displaying output).
|
||||
# tl;dr better simulates running Warp Dev/Stable
|
||||
OPEN_WITH_LAUNCHD=false
|
||||
|
||||
# Arguments to pass directly Warp (specified after --)
|
||||
# This is not supported when opening with launchd, as passing CLI arguments to
|
||||
# an application doesn't make sense.
|
||||
WARP_ARGS=()
|
||||
|
||||
# Export this variable so it can be referenced by app/build.rs later when
|
||||
# running `cargo bundle`.
|
||||
export FRAMEWORK_OVERRIDE="dev"
|
||||
|
||||
PARAMS=""
|
||||
while (( "$#" )); do
|
||||
case "$1" in
|
||||
--)
|
||||
shift
|
||||
WARP_ARGS=("$@")
|
||||
break
|
||||
;;
|
||||
--dont-open)
|
||||
DONT_OPEN=true
|
||||
shift
|
||||
;;
|
||||
--release)
|
||||
echo "Detected release build, pointing at release bundle under target/release/bundle"
|
||||
if [ "$WARP_CHANNEL" = "local" ]; then
|
||||
WARP_APP_PATH="target/release/bundle/osx/WarpLocal.app"
|
||||
else
|
||||
WARP_APP_PATH="target/release/bundle/osx/WarpOss.app"
|
||||
fi
|
||||
PARAMS="$PARAMS $1"
|
||||
shift
|
||||
;;
|
||||
--profile)
|
||||
PROFILE="$2"
|
||||
shift 2
|
||||
if [ "$WARP_CHANNEL" = "local" ]; then
|
||||
WARP_APP_PATH="target/${PROFILE}/bundle/osx/WarpLocal.app"
|
||||
else
|
||||
WARP_APP_PATH="target/${PROFILE}/bundle/osx/WarpOss.app"
|
||||
fi
|
||||
PARAMS="$PARAMS --profile $PROFILE"
|
||||
;;
|
||||
--open_with_launchd)
|
||||
OPEN_WITH_LAUNCHD=true
|
||||
shift
|
||||
;;
|
||||
--generate-schema)
|
||||
GENERATE_SCHEMA=true
|
||||
shift
|
||||
;;
|
||||
*) # preserve positional arguments
|
||||
PARAMS="$PARAMS $1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
rm -rf "$WARP_APP_PATH"
|
||||
|
||||
pushd app > /dev/null
|
||||
echo "Bundling app (bin: $WARP_BIN_NAME)..."
|
||||
cargo bundle --bin "$WARP_BIN_NAME" --features "$FEATURES" $PARAMS
|
||||
echo "Successfully bundled..."
|
||||
popd > /dev/null
|
||||
|
||||
if [[ ",$FEATURES," =~ ",cocoa_sentry," ]]; then
|
||||
echo "Copying Sentry framework into app bundle..."
|
||||
SENTRY_FRAMEWORK="app/frameworks/dev/Sentry-Dynamic-WithARM64e.xcframework/macos-arm64_arm64e_x86_64/Sentry.framework"
|
||||
cp -a "$SENTRY_FRAMEWORK" "$WARP_APP_PATH/Contents/Frameworks/"
|
||||
fi
|
||||
echo "Adding rpath to support mac frameworks (e.g. Sentry)"
|
||||
install_name_tool -add_rpath "@executable_path/../Frameworks" "$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME"
|
||||
|
||||
export WARP_SCHEME_NAME
|
||||
export WARP_PLIST_PATH="$WARP_APP_PATH/Contents/Info.plist"
|
||||
./script/update_plist
|
||||
|
||||
echo "Preparing bundled resources..."
|
||||
if [ "${GENERATE_SCHEMA:-false}" != "true" ]; then
|
||||
export SKIP_SETTINGS_SCHEMA=1
|
||||
fi
|
||||
NO_LICENSES=1 "${REPO_ROOT}/script/prepare_bundled_resources" "$WARP_APP_PATH/Contents/Resources" "$WARP_CHANNEL"
|
||||
|
||||
"${REPO_ROOT}/script/compile_icon" "$WARP_CHANNEL" "$WARP_APP_PATH"
|
||||
|
||||
if [[ ",$FEATURES," =~ ",heap_usage_tracking," ]]; then
|
||||
echo "Bundling pprof..."
|
||||
HELPERS_DIR="$WARP_APP_PATH/Contents/Helpers"
|
||||
"${REPO_ROOT}/script/prepare_bundled_pprof" "$HELPERS_DIR"
|
||||
fi
|
||||
|
||||
echo "Codesigning app..."
|
||||
SIGNING_CERT="$(security find-identity -p codesigning -v | grep "Apple Development" | awk '{print $2}' | head -1)"
|
||||
codesign --force --deep --options runtime --sign "${SIGNING_CERT:--}" "$WARP_APP_PATH" --entitlements script/Debug-Entitlements.plist
|
||||
|
||||
if [ "$DONT_OPEN" = false ] ; then
|
||||
if [ "$OPEN_WITH_LAUNCHD" = true ]; then
|
||||
echo "Launching with MacOS application launcher"
|
||||
PATH="" /usr/bin/open "./$WARP_APP_PATH"
|
||||
tail -f ~/Library/Logs/warp_local.log
|
||||
else
|
||||
echo "Opening app at ./$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME"
|
||||
"./$WARP_APP_PATH/Contents/MacOS/$WARP_BIN_NAME" "${WARP_ARGS[@]}"
|
||||
fi
|
||||
fi
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Clean up zip files on exit.
|
||||
cleanup() {
|
||||
if [[ -n "${zip_file:-}" ]] && [[ -f "$zip_file" ]]; then
|
||||
rm -f "$zip_file"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Parse arguments.
|
||||
DIR=""
|
||||
VERSION=""
|
||||
STATIC=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--dir)
|
||||
DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--static)
|
||||
STATIC=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
echo "Usage: $0 --dir <frameworks_dir> --version <version> [--static]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$DIR" ]] || [[ -z "$VERSION" ]]; then
|
||||
echo "Error: Both --dir and --version are required"
|
||||
echo "Usage: $0 --dir <frameworks_dir> --version <version> [--static]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure DIR is absolute or relative to current directory.
|
||||
if [[ ! "$DIR" = /* ]]; then
|
||||
DIR="$(pwd)/$DIR"
|
||||
fi
|
||||
|
||||
# Determine framework type.
|
||||
if [[ "$STATIC" == "true" ]]; then
|
||||
FRAMEWORK_TYPE="static"
|
||||
XCFRAMEWORK_NAME="Sentry.xcframework"
|
||||
else
|
||||
FRAMEWORK_TYPE="dynamic"
|
||||
XCFRAMEWORK_NAME="Sentry-Dynamic-WithARM64e.xcframework"
|
||||
fi
|
||||
|
||||
# Framework path.
|
||||
FRAMEWORK_PATH="$DIR/$XCFRAMEWORK_NAME/macos-arm64_arm64e_x86_64/Sentry.framework"
|
||||
|
||||
# Function to get framework version from Info.plist.
|
||||
get_framework_version() {
|
||||
local framework_path="$1"
|
||||
local plist_path="$framework_path/Resources/Info.plist"
|
||||
|
||||
if [[ ! -f "$plist_path" ]]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$plist_path" 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
# Fetch a URL with retries. Writes the response body to stdout.
|
||||
# Usage: fetch_with_retries <url> [curl_args...]
|
||||
fetch_with_retries() {
|
||||
local url="$1"
|
||||
shift
|
||||
local max_retries=3
|
||||
local attempt=1
|
||||
local wait_seconds=2
|
||||
|
||||
while (( attempt <= max_retries )); do
|
||||
local http_code
|
||||
local response
|
||||
|
||||
# Fetch the URL, capturing both the response body and the HTTP status code.
|
||||
# --fail-with-body ensures we still get the response body on HTTP errors.
|
||||
response=$(curl --fail-with-body --show-error --write-out "\n%{http_code}" "$@" "$url") || true
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
response=$(echo "$response" | sed '$d')
|
||||
|
||||
if [[ "$http_code" =~ ^2 ]]; then
|
||||
echo "$response"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Only retry on transient errors (rate limiting or server errors).
|
||||
local is_retryable=false
|
||||
if [[ "$http_code" == "429" || "$http_code" =~ ^5 ]]; then
|
||||
is_retryable=true
|
||||
fi
|
||||
|
||||
echo "[attempt $attempt/$max_retries] HTTP $http_code from $url" >&2
|
||||
if [[ "$is_retryable" == "true" ]] && (( attempt < max_retries )); then
|
||||
echo "Retrying in ${wait_seconds}s..." >&2
|
||||
sleep "$wait_seconds"
|
||||
wait_seconds=$(( wait_seconds * 2 ))
|
||||
attempt=$(( attempt + 1 ))
|
||||
else
|
||||
if [[ "$is_retryable" != "true" ]]; then
|
||||
echo "Non-retryable HTTP status; giving up." >&2
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Error: Failed to fetch $url (HTTP $http_code). Response:" >&2
|
||||
echo "$response" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to download and extract framework.
|
||||
download_framework() {
|
||||
local xcframework_name="$1"
|
||||
local xcframework_dir="$DIR/$xcframework_name"
|
||||
local download_url="https://github.com/getsentry/sentry-cocoa/releases/download/${VERSION}/${xcframework_name}.zip"
|
||||
zip_file="$DIR/${xcframework_name}.zip"
|
||||
|
||||
echo "Downloading $xcframework_name version $VERSION..."
|
||||
|
||||
# Create directory if it doesn't exist.
|
||||
mkdir -p "$DIR"
|
||||
|
||||
# Require jq for SHA256 verification.
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required for SHA256 verification"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fetch release metadata from GitHub API.
|
||||
# Prefer `gh api` for authenticated requests (avoids GitHub rate limits in CI).
|
||||
local api_url="https://api.github.com/repos/getsentry/sentry-cocoa/releases/tags/${VERSION}"
|
||||
echo "Fetching release metadata from $api_url"
|
||||
local api_response
|
||||
if command -v gh &> /dev/null; then
|
||||
if ! api_response=$(gh api "repos/getsentry/sentry-cocoa/releases/tags/${VERSION}"); then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! api_response=$(fetch_with_retries "$api_url" --silent); then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate that the response contains an assets array before trying to iterate it.
|
||||
local assets_type
|
||||
assets_type=$(echo "$api_response" | jq -r '.assets | type')
|
||||
if [[ "$assets_type" != "array" ]]; then
|
||||
echo "Error: GitHub API response does not contain an assets array (got: $assets_type)." >&2
|
||||
echo "Raw API response:" >&2
|
||||
echo "$api_response" | head -50 >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local expected_sha256
|
||||
expected_sha256=$(echo "$api_response" | jq -r ".assets[] | select(.name == \"$xcframework_name.zip\") | .digest // empty" | cut -d: -f2)
|
||||
|
||||
if [[ -z "$expected_sha256" ]]; then
|
||||
echo "Error: Could not find SHA256 checksum for $xcframework_name.zip in release $VERSION."
|
||||
echo "Available assets:"
|
||||
echo "$api_response" | jq -r '.assets[].name'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Expected SHA256: $expected_sha256"
|
||||
|
||||
# Download the framework. Prefer `gh release download` for authentication.
|
||||
echo "Downloading from $download_url"
|
||||
if command -v gh &> /dev/null; then
|
||||
if ! gh release download "${VERSION}" --repo getsentry/sentry-cocoa --pattern "${xcframework_name}.zip" --dir "$DIR" --clobber; then
|
||||
echo "Error: Failed to download $xcframework_name via gh CLI"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! curl -L -f --show-error -o "$zip_file" "$download_url"; then
|
||||
echo "Error: Failed to download $xcframework_name from $download_url"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify checksum if we have one.
|
||||
if [[ -n "$expected_sha256" ]]; then
|
||||
echo "Verifying checksum..."
|
||||
local actual_sha256
|
||||
actual_sha256=$(shasum -a 256 "$zip_file" | awk '{print $1}')
|
||||
|
||||
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
|
||||
echo "Error: SHA256 checksum mismatch!"
|
||||
echo " Expected: $expected_sha256"
|
||||
echo " Actual: $actual_sha256"
|
||||
exit 1
|
||||
fi
|
||||
echo "Checksum verified successfully"
|
||||
fi
|
||||
|
||||
# Remove existing framework if present.
|
||||
if [[ -d "$xcframework_dir" ]]; then
|
||||
rm -rf "$xcframework_dir"
|
||||
fi
|
||||
|
||||
# Extract only the macos-arm64_arm64e_x86_64 subdirectory.
|
||||
echo "Extracting $xcframework_name (macOS frameworks only)..."
|
||||
if ! unzip -q "$zip_file" "${xcframework_name}/macos-arm64_arm64e_x86_64/*" -d "$DIR"; then
|
||||
echo "Error: Failed to extract framework from zip. Expected path: ${xcframework_name}/macos-arm64_arm64e_x86_64/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully installed $xcframework_name version $VERSION"
|
||||
}
|
||||
|
||||
# Check and download the needed framework.
|
||||
CURRENT_VERSION=$(get_framework_version "$FRAMEWORK_PATH")
|
||||
if [[ -z "$CURRENT_VERSION" ]] || [[ "$CURRENT_VERSION" != "$VERSION" ]]; then
|
||||
if [[ -z "$CURRENT_VERSION" ]]; then
|
||||
echo "$FRAMEWORK_TYPE framework not found or missing Info.plist"
|
||||
else
|
||||
echo "$FRAMEWORK_TYPE framework version mismatch: found $CURRENT_VERSION, expected $VERSION"
|
||||
fi
|
||||
download_framework "$XCFRAMEWORK_NAME"
|
||||
else
|
||||
echo "$FRAMEWORK_TYPE framework version $VERSION already installed"
|
||||
fi
|
||||
|
||||
echo "Framework up to date"
|
||||
Reference in New Issue
Block a user