Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.personal-information.addressbook</key>
<true/>
<key>com.apple.security.personal-information.calendars</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict>
</plist>
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.personal-information.addressbook</key>
<true/>
<key>com.apple.security.personal-information.calendars</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>2BBY89MBSN.dev.warp</string>
</array>
</dict>
</plist>
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
OS_TYPE="$(uname -s)"
if [[ "$OS_TYPE" = "Darwin" ]]; then
./script/macos/bootstrap "$@"
elif [[ "$OS_TYPE" = "Linux" ]]; then
./script/linux/bootstrap "$@"
elif [[ "$OS_TYPE" =~ ^[MINGW64_NT|MSYS_NT] ]]; then
./script/windows/bootstrap.ps1 "$@"
else
echo "No bootstrap script defined for the current platform!"
exit 1
fi
Executable
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
OS_TYPE="$(uname -s)"
if [[ "$OS_TYPE" = "Darwin" ]]; then
./script/macos/bundle "$@"
elif [[ "$OS_TYPE" = "Linux" ]]; then
./script/linux/bundle "$@"
elif [[ "$OS_TYPE" =~ ^[MINGW64_NT|MSYS_NT] ]]; then
while (( "$#" )); do
# Turn double hyphens into single hyphens.
PROCESSED_PARAM=$( echo $1 | sed -e "s/^--/-/" )
case "$PROCESSED_PARAM" in
# Powershell has a built-in argument called `debug`,
# so we must pass that argument under a different name.
-debug)
# rename debug --> debug_build
PARAMS="$PARAMS -debug_build"
shift
;;
*)
# preserve other arguments as-is
PARAMS="$PARAMS $PROCESSED_PARAM"
shift
;;
esac
done
./script/windows/bundle.ps1 "$PARAMS"
else
echo "No bundle script defined for the current platform ($OS_TYPE)!"
exit 1
fi
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# Verifies that the accepted license lists in deny.toml and about.toml are in
# sync. Exits non-zero with a diff if they diverge.
set -e
REPO_ROOT="$(git rev-parse --show-toplevel)"
python3 -c "
import tomllib, sys
with open('$REPO_ROOT/deny.toml', 'rb') as f:
deny = set(tomllib.load(f)['licenses']['allow'])
with open('$REPO_ROOT/about.toml', 'rb') as f:
about = set(tomllib.load(f)['accepted'])
if deny != about:
only_deny = deny - about
only_about = about - deny
if only_deny:
print(f'In deny.toml but not about.toml: {sorted(only_deny)}')
if only_about:
print(f'In about.toml but not deny.toml: {sorted(only_about)}')
sys.exit(1)
print('License config in sync.')
"
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
#
# Compile a .icon bundle using actool and update the app's Info.plist
# to support macOS Sequoia's icon tinting feature.
#
# Usage: compile_icon <channel> <app_bundle_path>
# channel: The release channel (e.g., stable)
# app_bundle_path: Path to the .app bundle (e.g., Warp.app)
set -e
if [ "$#" -ne 2 ]; then
echo "Usage: compile_icon <channel> <app_bundle_path>"
exit 1
fi
CHANNEL="$1"
APP_BUNDLE_PATH="$2"
# Determine the repository root directory
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ICON_BUNDLE_PATH="$REPO_ROOT/app/channels/$CHANNEL/icon/AppIcon.icon"
# Only compile if the .icon bundle exists for this channel.
if [[ ! -d "$ICON_BUNDLE_PATH" ]]; then
if [[ "$CHANNEL" = "oss" ]]; then
echo "Warning: no .icon bundle found for $CHANNEL channel at $ICON_BUNDLE_PATH; skipping adaptive icon compilation." >&2
exit 0
fi
echo "Error: .icon bundle not found at $ICON_BUNDLE_PATH" >&2
exit 1
fi
echo "Compiling .icon bundle for $CHANNEL channel"
BUNDLED_RESOURCES_DIR="$APP_BUNDLE_PATH/Contents/Resources"
PARTIAL_INFO_PLIST="$(dirname "$APP_BUNDLE_PATH")/partial-icon-info.plist"
# Compile the .icon bundle using actool
xcrun actool \
--compile "$BUNDLED_RESOURCES_DIR" \
--platform macosx \
--minimum-deployment-target 10.14 \
--app-icon AppIcon \
--output-partial-info-plist "$PARTIAL_INFO_PLIST" \
"$ICON_BUNDLE_PATH"
# Earlier XCode versions won't build the correct asset format for adaptive icons
if [[ ! -f "$BUNDLED_RESOURCES_DIR/Assets.car" ]]; then
XCODE_VERSION=$(xcodebuild -version 2>/dev/null | head -1 || echo "unknown")
echo "Warning: actool did not produce Assets.car which is required for the latest App icon format." >&2
echo "(Note that XCode version <26 does not support .icon bundles. Your version: $XCODE_VERSION)." >&2
fi
# Update Info.plist to reference AppIcon instead of the old .icns
plutil -replace CFBundleIconFile -string "AppIcon" "$APP_BUNDLE_PATH/Contents/Info.plist"
plutil -insert CFBundleIconName -string "AppIcon" "$APP_BUNDLE_PATH/Contents/Info.plist" 2>/dev/null || \
plutil -replace CFBundleIconName -string "AppIcon" "$APP_BUNDLE_PATH/Contents/Info.plist"
# Get the app name from the bundle
APP_NAME=$(basename "$APP_BUNDLE_PATH" .app)
echo "Icon compiled successfully."
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
#
# Copy channel-gated bundled skills into the app bundle.
#
# Usage:
# copy_conditional_skills <channel> <gated_skills_src> <dest_skills_dir>
#
# Arguments:
# channel: Release channel (local, dev, preview, stable).
# gated_skills_src: Path to resources/channel-gated-skills/.
# dest_skills_dir: Destination skills directory (e.g. .../bundled/skills/).
#
# Gating is progressive: earlier gates include all skills from later gates.
# For example, a dogfood build includes skills from both dogfood/ and
# preview/. The stable channel has no gate — stable-ready skills belong
# in resources/skills/ (the always-bundled directory).
set -e
if [ $# -ne 3 ]; then
echo "Usage: $0 <channel> <gated_skills_src> <dest_skills_dir>" >&2
exit 1
fi
CHANNEL="$1"
GATED_SKILLS_SRC="$2"
DEST_SKILLS_DIR="$3"
# Gate labels ordered from most-inclusive to least-inclusive.
# A channel's gate includes all gates at or after its position.
GATE_ORDER=("dogfood" "preview")
if [ ! -d "$GATED_SKILLS_SRC" ]; then
exit 0
fi
# Error out if a stable/ gate directory exists — stable skills should live
# in the always-bundled resources/skills/ directory instead.
if [ -d "$GATED_SKILLS_SRC/stable" ]; then
echo "Error: found a 'stable/' directory in $GATED_SKILLS_SRC." >&2
echo "The stable channel does not use gated skills. Move stable-ready skills to resources/skills/ (the always-bundled directory) instead." >&2
exit 1
fi
# Map the release channel to its gate label.
case "$CHANNEL" in
local|dev) GATE="dogfood" ;;
preview) GATE="preview" ;;
*)
echo " Channel '$CHANNEL' has no gated skills, skipping"
exit 0
;;
esac
# Build the set of included gates (progressive: this gate and all after it).
INCLUDED_GATES=()
FOUND=false
for g in "${GATE_ORDER[@]}"; do
if [ "$g" = "$GATE" ]; then
FOUND=true
fi
if [ "$FOUND" = true ]; then
INCLUDED_GATES+=("$g")
fi
done
# Helper: check if a value is in the included gates list.
is_included() {
local needle="$1"
for g in "${INCLUDED_GATES[@]}"; do
if [ "$g" = "$needle" ]; then
return 0
fi
done
return 1
}
# Iterate over gate directories and copy matching skills.
for gate_dir in "$GATED_SKILLS_SRC"/*/; do
# Skip if the glob didn't match anything.
[ -d "$gate_dir" ] || continue
gate_name="$(basename "$gate_dir")"
if ! is_included "$gate_name"; then
# List the skills that would have been included for the skip message.
skills=""
for skill_dir in "$gate_dir"/*/; do
[ -d "$skill_dir" ] || continue
if [ -n "$skills" ]; then
skills="$skills, "
fi
skills="$skills$(basename "$skill_dir")"
done
echo " Skipping gate '$gate_name' (channel '$CHANNEL') — would include: $skills"
continue
fi
for skill_dir in "$gate_dir"/*/; do
[ -d "$skill_dir" ] || continue
skill_name="$(basename "$skill_dir")"
dest="$DEST_SKILLS_DIR/$skill_name"
echo " Copying gated skill: $skill_name (gate: $gate_name)"
mkdir -p "$dest"
cp -R "$skill_dir"/. "$dest"/
done
done
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
PARAMS=""
while (( "$#" )); do
case "$1" in
--branch-name)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
GIT_BRANCH_NAME=$2
shift 2
else
echo >&2 "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--channel)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
CHANNEL=$2
shift 2
else
echo >&2 "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"
RELEASE_BRANCH_PREFIX="${CHANNEL}_release"
git fetch --all --tags
if [[ "$GIT_BRANCH_NAME" == "$RELEASE_BRANCH_PREFIX/"* ]]; then
echo >&2 "Already on release branch $GIT_BRANCH_NAME, not creating new one."
tag=$(git tag | grep "${GIT_BRANCH_NAME#"$RELEASE_BRANCH_PREFIX/"}" | sort -r --version-sort | head -n1)
echo >&2 "Current tag on branch is $tag"
if git rev-parse "$tag" >/dev/null 2>&1; then
# Tag already exists, increase the RC number
suffix=${tag: -2}
suffix=$((10#$suffix+1))
suffix=$(printf '%02d' $suffix)
tag_length=${#tag}
non_rc=${tag:0:$(($tag_length - 3))}
tag="${non_rc}_$suffix"
echo >&2 "Creating tag $tag"
git tag "$tag"
git push origin "$tag"
echo "$tag"
else
echo >&2 "No tag found on release branch, something is wrong"
exit 1
fi
else
# Creating release branch and new tag and version _00
date_formatted=$(date +'%Y.%m.%d.%H.%M')
tag="v0.$date_formatted.${CHANNEL}_00"
echo >&2 "Creating tag $tag"
git tag "$tag"
git push origin "$tag"
release_branch_name="v0.$date_formatted.${CHANNEL}"
echo >&2 "Creating release branch $RELEASE_BRANCH_PREFIX/$release_branch_name"
git checkout -b "$RELEASE_BRANCH_PREFIX/$release_branch_name"
git push -u origin "$RELEASE_BRANCH_PREFIX/$release_branch_name"
echo "$tag"
fi
export tag
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env bash
#
# Cross-compiles the Oz CLI for Linux x86_64 (musl) on macOS and uploads it
# to a remote host via rsync for local remote-server development.
#
# Uses rsync for delta transfers — after the first deploy, only changed bytes
# are sent, which is dramatically faster for iterative development.
#
# Prerequisites:
# brew install filosottile/musl-cross/musl-cross
# rustup target add x86_64-unknown-linux-musl
#
# Usage:
# script/deploy_remote_server --host user@hostname [--profile release]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Defaults
PROFILE_MODE="dev-remote"
HOST=""
usage() {
cat <<EOF
Usage: $(basename "$0") --host <user@hostname> [OPTIONS]
Cross-compile the Oz CLI for Linux x86_64 and upload it to a remote host.
Required:
--host <user@hostname> Remote host to upload to
Options:
--profile <profile> Build profile: dev-remote (default, strips symbols), dev, release, or optimized
Use --profile dev if you need symbols for remote debugging
--help Show this help message
EOF
exit 0
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
HOST="$2"
shift 2
;;
--profile)
PROFILE_MODE="$2"
shift 2
;;
--help)
usage
;;
*)
echo "Error: Unknown argument: $1" >&2
echo "Run with --help for usage." >&2
exit 1
;;
esac
done
# Validate required arguments
if [[ -z "$HOST" ]]; then
echo "Error: --host is required." >&2
echo "Run with --help for usage." >&2
exit 1
fi
# Validate profile
case "$PROFILE_MODE" in
dev-remote|dev|release|optimized) ;;
*)
echo "Error: Unsupported profile '$PROFILE_MODE'. Use 'dev-remote', 'dev', 'release', or 'optimized'." >&2
exit 1
;;
esac
# Check for musl-cross linker
if ! command -v x86_64-linux-musl-gcc &>/dev/null; then
echo "Error: x86_64-linux-musl-gcc not found." >&2
echo "Install it with: brew install filosottile/musl-cross/musl-cross" >&2
exit 1
fi
# Check for musl target
if ! rustup target list --installed 2>/dev/null | grep -q x86_64-unknown-linux-musl; then
echo "Error: x86_64-unknown-linux-musl target not installed." >&2
echo "Install it with: rustup target add x86_64-unknown-linux-musl" >&2
exit 1
fi
# Determine build parameters
TARGET="x86_64-unknown-linux-musl"
case "$PROFILE_MODE" in
dev-remote)
CARGO_PROFILE="dev-remote"
;;
dev)
CARGO_PROFILE="dev"
;;
release)
CARGO_PROFILE="release"
;;
optimized)
CARGO_PROFILE="release-lto-debug_assertions"
;;
esac
FEATURES="release_bundle,crash_reporting,standalone,agent_mode_debug"
WARP_BIN="warp"
BINARY_NAME="oz-local"
REMOTE_DIR=".warp-local/remote-server"
# Determine the output directory
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT/target}"
case "$CARGO_PROFILE" in
dev)
OUTPUT_DIR="$CARGO_TARGET_DIR/$TARGET/debug"
;;
*)
OUTPUT_DIR="$CARGO_TARGET_DIR/$TARGET/$CARGO_PROFILE"
;;
esac
BUILT_BINARY="$OUTPUT_DIR/$WARP_BIN"
echo "==> Building Oz CLI for $TARGET (profile=$CARGO_PROFILE)"
echo " Binary: $WARP_BIN -> $BINARY_NAME"
echo " Features: $FEATURES"
echo ""
# Build with linker and rustflags overrides to avoid macOS-specific flags
# from .cargo/config.toml
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-C symbol-mangling-version=v0" \
cargo build \
-p warp \
--bin "$WARP_BIN" \
--target "$TARGET" \
--profile "$CARGO_PROFILE" \
--features "$FEATURES"
if [[ ! -f "$BUILT_BINARY" ]]; then
echo "Error: Expected binary not found at $BUILT_BINARY" >&2
exit 1
fi
BINARY_SIZE=$(du -h "$BUILT_BINARY" | cut -f1)
echo ""
echo "==> Build complete ($BINARY_SIZE)"
# Resolve $HOME on the remote so our upload lands in the same directory the
# Warp client checks. The client runs `test -x ~/.warp-local/...` and lets
# the remote login shell expand `~` to $HOME, while rsync's remote path
# expansion can differ (e.g. on Namespace devboxes, the initial directory
# is /workspaces but $HOME is elsewhere). Using an absolute path derived
# from the remote $HOME keeps both sides consistent.
REMOTE_HOME=$(ssh "$HOST" 'printf %s "$HOME"')
if [[ -z "$REMOTE_HOME" ]]; then
echo "Error: could not resolve remote \$HOME on $HOST" >&2
exit 1
fi
REMOTE_ABS_DIR="$REMOTE_HOME/$REMOTE_DIR"
# Ensure rsync is available on the remote host
if ! ssh "$HOST" "command -v rsync" &>/dev/null; then
echo "==> rsync not found on remote host, installing..."
ssh "$HOST" "sudo apt-get install -y rsync || sudo yum install -y rsync || sudo dnf install -y rsync || sudo apk add rsync"
if ! ssh "$HOST" "command -v rsync" &>/dev/null; then
echo "Error: failed to install rsync on $HOST. Please install it manually and re-run." >&2
exit 1
fi
fi
# Ensure the remote directory exists
ssh "$HOST" "mkdir -p $REMOTE_ABS_DIR"
echo "==> Uploading to $HOST:$REMOTE_ABS_DIR/$BINARY_NAME"
# Upload via rsync with delta transfer and compression.
# After the first deploy, only changed bytes are transferred.
rsync -z -t --partial --progress \
"$BUILT_BINARY" \
"$HOST:$REMOTE_ABS_DIR/$BINARY_NAME"
# Set executable permissions (done separately for openrsync compatibility on macOS)
ssh "$HOST" "chmod 755 $REMOTE_ABS_DIR/$BINARY_NAME"
echo ""
echo "==> Done! Binary deployed to $HOST:$REMOTE_ABS_DIR/$BINARY_NAME"
echo " (resolved from ~/$REMOTE_DIR/$BINARY_NAME on remote)"
+68
View File
@@ -0,0 +1,68 @@
'''
Generates the `ExternalFontFamily` definitions used in `app/src/font_fallback.rs`.
These definitions contain the URLs to each external fallback font we use in Warp.
Generated code is sent to stdout.
This script will read our cloud storage bucket to retrieve the names of the fonts
we support, and generate the code required to initialize static references for
each font family.
Assumes that the fallback fonts in the prod `warp-static-assets` bucket are
identical to the ones stored in the staging `warp-server-staging-static-assets`
bucket.
Usage:
1. Make sure the gcloud CLI is installed and you are authed via `gcloud auth login`.
2. Run `python3 generate_families.py`
3. Manually inspect the name for each font. The script will generate the name in
title-case, but this isn't correct for some fonts (e.g. Noto Sans SC).
'''
import subprocess
from collections import defaultdict
def list_fonts():
command = "gcloud storage ls --recursive 'gs://warp-static-assets/fallback-fonts/**.ttf'"
return subprocess.check_output(command, shell=True, text=True).splitlines()
def generate_families(font_uris):
family_map = defaultdict(list)
for uri in font_uris:
parts = uri.removeprefix("gs://warp-static-assets/fallback-fonts/").split('/')
family_name = parts[0]
font_name = parts[1]
family_map[family_name].append(font_name)
for family_name, font_names in family_map.items():
print_family(family_name, font_names)
def indent_level(level, s):
indent = " " * level
return indent + s
def print_family(family_name, font_names):
variable_name = family_name.replace('-', '_').upper()
title_case_name = family_name.replace('-', ' ').title()
print(f"static ref {variable_name}: ExternalFontFamily = ExternalFontFamily {{")
# Title-case is not correct for some fonts, e.g. "Noto Sans SC", so we add
# a todo to make any manual adjustments.
print(indent_level(1, f"name: \"{title_case_name}\", // TODO: double-check the title is correct"))
print(indent_level(1, "font_urls: Arc::new(vec!["))
for font_name in font_names:
print(indent_level(2, f"url_for_font(\"{family_name}\", \"{font_name}\"),"))
print(indent_level(1, "]),"))
print("};")
def main():
font_uris = list_fonts()
generate_families(font_uris)
if __name__ == "__main__":
main()
+255
View File
@@ -0,0 +1,255 @@
'''
Generates the match statement used in `app/src/font_fallback.rs` to map Unicode
code points to fallback fonts. Should be used in tandem with `generate-families.py`.
Generated code is sent to stdout.
This script will read our cloud storage bucket to download the font data for each
fallback font that we support. A directory "downloaded_fonts" will be created in
the directory where the script is executed to contain the downloaded fonts. If
that directory already exists, it is assumed that the fonts have previously been
downloaded and skips downloading them again.
Assumptions:
- The fallback fonts in the prod `warp-static-assets` bucket are identical to the
ones stored in the staging `warp-server-staging-static-assets` bucket.
- For each font family in the bucket, there is a variant that contains "Regular"
in the filename.
Usage:
1. Install the dependencies in `requirements.txt`.
2. Make sure the gcloud CLI is installed and you are authed via `gcloud auth login`.
3. Make sure you're running the script from `scripts/font_fallback`.
4. Run `python3 generate-mappings.py`.
'''
import os
import subprocess
import sys
from operator import itemgetter
from fontTools.ttLib import TTFont
from collections import defaultdict
# Represents priority for each font, with a lower value being higher priority.
# Since a code point can be supported by multiple fonts, the script will map the
# code point to the font with more priority.
GLOBAL_ORDER = {
"Hack Nerd Font": 1,
"Noto Color Emoji": 2,
"Noto Sans Symbols": 3,
"Noto Sans Symbols 2": 4,
"Noto Sans SC": 5,
"Noto Sans JP": 6,
"Noto Sans Devanagari": 7,
}
# By default, we try to coalesce code point ranges. e.g. If we have the
# following mappings:
# U+10000..U+10002 -> Noto Sans SC
# U+10004..U+10005 -> Noto Sans SC
# If U+10003 is not represented by any font, we will merge these code points into
# a single range even though there is an unsupported code point in the range:
# U+10000..U+10005 -> Noto Sans SC
# This is done to reduce the total number of mappings and is useful for handling
# Unicode blocks with rare characters where font support for these characters is
# sparse.
#
# However, we don't want to do this for some fonts where unsupported code points
# could represent icons or private use areas in Unicode. Those fonts are added
# to this set.
FONTS_NOT_TO_COALESCE = set([
"Noto Sans Symbols",
"Noto Sans Symbols 2",
"Noto Color Emoji",
"Hack Nerd Font"
])
HACK_FONT_FILEPATH = "../../app/assets/bundled/fonts/hack/Hack-Regular.ttf"
ROBOTO_FONT_FILEPATH = "../../app/assets/bundled/fonts/roboto/Roboto-Regular.ttf"
FONT_DOWNLOAD_DIR = "./downloaded_fonts"
def download_fallback_fonts():
if os.path.exists(FONT_DOWNLOAD_DIR):
# Fonts already exist, no need to download
return
os.mkdir(FONT_DOWNLOAD_DIR)
command = f"gcloud storage cp 'gs://warp-static-assets/fallback-fonts/**/*Regular*.ttf' '{FONT_DOWNLOAD_DIR}'"
return_code = subprocess.call(command, shell=True)
if return_code != 0:
sys.exit("Failed to download fonts from GCP")
def get_global_order(font_name):
if font_name in GLOBAL_ORDER:
return GLOBAL_ORDER[font_name]
else:
return len(GLOBAL_ORDER) + 1
def get_default_fonts():
return [TTFont(HACK_FONT_FILEPATH), TTFont(ROBOTO_FONT_FILEPATH)]
# Returns a `TTFont` object for each fallback font, sorted by their global order.
def get_fallback_fonts(fallback_fonts_dir):
fonts = []
for file in os.listdir(fallback_fonts_dir):
if file.endswith(".ttf"):
path = os.path.join(fallback_fonts_dir, file)
font = TTFont(path)
font_name = get_font_name(font)
global_order = get_global_order(font_name)
fonts.append((font, global_order))
fonts.sort(key=itemgetter(1))
return [font for (font, _) in fonts]
def get_font_name(font):
return font['name'].getBestFamilyName()
def supported_code_points(font):
code_points = set()
for table in font['cmap'].tables:
if table.isUnicode():
code_points.update(table.cmap.keys())
return code_points
def common_code_points(fonts):
code_points = [supported_code_points(font) for font in fonts]
return set.intersection(*code_points)
def generate_mapping(default_fonts, fallback_fonts):
default_code_points = common_code_points(default_fonts)
mapping = {}
for font in reversed(fallback_fonts):
font_name = get_font_name(font)
font_code_points = supported_code_points(font)
for code_point in font_code_points:
if code_point in default_code_points:
continue
mapping[code_point] = font_name
ranges = coalesce_ranges(collapse_to_ranges(mapping))
font_ranges_map = collect_ranges_to_map(ranges)
print_match_statement(font_ranges_map)
# Takes a mapping of individual code points -> fallback fonts and merges them
# into ranges where consecutive code points map to the same fallback font.
def collapse_to_ranges(mapping):
mapping_list = [(k, v) for k, v in mapping.items()]
mapping_list.sort(key=itemgetter(0))
ranges = []
prev_font = None
active_range = None
for code_point, font in mapping_list:
if active_range and prev_font and (active_range[1], prev_font) != (code_point - 1, font):
ranges.append((active_range, prev_font))
active_range = None
if not active_range:
active_range = (code_point, code_point)
else:
active_range = (active_range[0], code_point)
prev_font = font
if active_range and prev_font:
ranges.append((active_range, prev_font))
return ranges
# Takes a mapping of code point ranges -> fallback fonts and coalesces them. See
# the comment on `FONTS_NOT_TO_COALESCE` for more details on coalescing.
def coalesce_ranges(ranges):
new_ranges = []
prev_font = None
active_range = None
for cur_range, font in ranges:
if font in FONTS_NOT_TO_COALESCE:
if active_range and prev_font:
new_ranges.append((active_range, prev_font))
new_ranges.append((cur_range, font))
prev_font = None
active_range = None
continue
if active_range and prev_font and prev_font != font:
new_ranges.append((active_range, prev_font))
active_range = None
if not active_range:
active_range = cur_range
else:
active_range = (active_range[0], cur_range[1])
prev_font = font
if active_range and prev_font:
ranges.append((active_range, prev_font))
return new_ranges
# Collects the mappings into a dictionary where each font is mapped to a list of
# all the code point ranges that it supports.
def collect_ranges_to_map(ranges):
font_ranges_map = defaultdict(list)
for cur_range, font in ranges:
font_ranges_map[font].append(cur_range)
return font_ranges_map
def match_arm(code_point_range, font_name):
range_start, range_end = code_point_range
constant_case_font_name = font_name.replace(" ", "_").upper()
match_start = f"\\u{{{range_start:04X}}}"
match_end = f"\\u{{{range_end:04X}}}"
font_family = f"Some({constant_case_font_name}.clone())"
return f"'{match_start}'..='{match_end}' => {font_family},"
def print_font_ranges(font_name, ranges):
constant_case_font_name = font_name.replace(" ", "_").upper()
font_family = f"Some({constant_case_font_name}.clone())"
for i, (range_start, range_end) in enumerate(ranges):
match_start = f"\\u{{{range_start:04X}}}"
match_end = f"\\u{{{range_end:04X}}}"
line = ""
if i > 0:
line += "| "
line += f"'{match_start}'..='{match_end}'"
if i == len(ranges) - 1:
line += f" => {font_family},"
print(line)
def print_match_statement(font_ranges_map):
print("match ch {")
for font_name, ranges in font_ranges_map.items():
print_font_ranges(font_name, ranges)
print("_ => None")
print("}")
def main():
default_fonts = get_default_fonts()
download_fallback_fonts()
fallback_fonts = get_fallback_fonts(FONT_DOWNLOAD_DIR)
generate_mapping(default_fonts, fallback_fonts)
if __name__ == "__main__":
main()
+1
View File
@@ -0,0 +1 @@
fonttools==4.61.0
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
#
# Installs cargo-binstall if not present, or updates it if already installed.
# This script should be platform-agnostic (eg. no unix-only references like /dev/null).
# Install cargo-binstall, then/or use it to update itself.
if ! command -v cargo-binstall; then
cargo install cargo-binstall@1.14.3 --locked
fi
cargo binstall cargo-binstall
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
#
# Installs all cargo-managed dependencies required to build Warp.
# This script should be platform-agnostic (eg. no unix-only references like /dev/null).
"$PWD"/script/install_cargo_binstall
# Use cargo-binstall to install the Diesel CLI.
#
# This is only needed for local development, so we skip it when running on GitHub
# (either for CI or building a release).
if [ "${GITHUB_ACTIONS}" != "true" ]; then
cargo binstall --force -y diesel_cli
fi
# Install the internal channel config binary. This will fail gracefully for
# external contributors who don't have access to the private configuration.
"$PWD"/script/install_channel_config || echo "Skipping internal channel config installation (no repo access)."
if [ "$(uname -s)" = "Darwin" ]; then
"$PWD"/script/macos/install_build_deps
fi
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# Script to install cargo-bundle for CI. This is used as an optimization to speed up CI instead of having to install and
# compile cargo-bundle on each CI workflow run.
# This script should be platform-agnostic (eg. no unix-only references like /dev/null).
cargo_location=$(dirname "$(which cargo)")
if test -f "$cargo_location/cargo-bundle"; then
echo "cargo-bundle already exists, not installing."
else
set -e
curl https://storage.googleapis.com/cached_crates/cargo-bundle.zip --output cargo-bundle.zip
unzip -o cargo-bundle.zip
chmod 755 cargo-bundle
cp cargo-bundle "$cargo_location"
echo "Successfully moved cached cargo-bundle to location $cargo_location"
fi
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# Installs all cargo-managed dependencies required to build Warp release
# artifacts (e.g., bundled license generation).
# This script should be platform-agnostic (eg. no unix-only references like /dev/null).
NO_BUILD_DEPS=false
while [[ $# -gt 0 ]]; do
case $1 in
--no-build-deps)
NO_BUILD_DEPS=true
shift
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
if [ "$NO_BUILD_DEPS" = false ]; then
"$PWD"/script/install_cargo_build_deps
fi
# Install cargo-about for generating third-party license attribution.
cargo install --locked cargo-about@0.8.4
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
#
# Installs all cargo-managed dependencies required to build and test Warp.
# This script should be platform-agnostic (eg. no unix-only references like /dev/null).
"$PWD"/script/install_cargo_build_deps
# note: keep this version in sync with wgslfmt in .github/workflows/ci.yml
cargo install --git https://github.com/wgsl-analyzer/wgsl-analyzer --tag "2025-06-28" wgslfmt
"$PWD"/script/install_cargo_binstall
# Install nextest, which we use as our test execution harness.
cargo binstall --secure --no-confirm --no-discover-github-token cargo-nextest
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# Installs the warp_channel_config binary at a pinned revision.
#
# This script acts as a lockfile: the REVISION variable pins the exact version
# of the config binary to the code that reads its output. Update REVISION when
# the config binary changes.
#
# Usage:
# ./script/install_channel_config # install if not already at pinned rev
# ./script/install_channel_config --force # force reinstall
REPO="ssh://git@github.com/warpdotdev/warp-channel-config.git"
REVISION="97a199b5bba11af9c7d3bd43dfb3669316d1f0f8"
BIN_NAME="warp-channel-config"
# Check for repo access before attempting to `cargo install` the binary.
if ! git ls-remote --exit-code "${REPO}" HEAD >/dev/null 2>&1; then
echo "Cannot access ${REPO} (no SSH access?). Skipping install."
exit 1
fi
FORCE_FLAG=""
if [ "${1:-}" = "--force" ]; then
FORCE_FLAG="--force"
fi
cargo install ${FORCE_FLAG} --git "${REPO}" --rev "${REVISION}" --bin "${BIN_NAME}"
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
#
# Script to install Rust for CI.
#
# This script should be platform-agnostic (eg. no unix-only references like /dev/null).
set -e
# Define some control sequences for text formatting.
red="\033[0;31m"
reset="\033[0m"
CI_FLAGS=""
if [ "${GITHUB_ACTIONS}" == "true" ]; then
CI_FLAGS="-s -- -y"
fi
# Install Rust.
if ! command -v cargo; then
echo "⬇️ Installing rust..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $CI_FLAGS
CARGO_ENV_FILE="$HOME/.cargo/env"
if [[ -f "$CARGO_ENV_FILE" ]]; then
source "$CARGO_ENV_FILE"
else
echo -e "⚠️ ${red}Please start a new terminal session so that cargo is in your PATH.${reset}"
exit 1
fi
fi
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env -S pwsh -NoProfile
param(
[switch]$ci,
[switch]$fix
)
$rulesFile = './.PSScriptAnalyzerSettings.psd1'
$sources = @(
'./app/assets/bundled/bootstrap/',
'./script/windows/'
)
foreach ($source in $sources) {
Write-Output "Inspecting scripts in '$source'"
$scriptAnalyzerParams = @{
Settings = $rulesFile
Path = $source
ReportSummary = $true
Recurse = $true
Fix = $fix
}
$results = Invoke-ScriptAnalyzer @scriptAnalyzerParams
Write-Output $results
$errorsAndWarnings = $results | Where-Object { $_.Severity -ge [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticSeverity]::Warning }
$problemCount = ($errorsAndWarnings | Measure-Object).Count
if ($problemCount -gt 0 -and $ci) {
throw "Lint failed with $problemCount problems"
}
}
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -e
# Update the apt cache before installing dependencies. It can be slow, so the install scripts
# don't do it (to avoid doing it more than once).
sudo apt update -y
# Install all dependencies needed to build, run, and test Warp.
"$PWD"/script/linux/install_test_deps
# Install linuxdeploy.
"$PWD"/script/linux/install_linuxdeploy
# Make sure we're authenticated with the gcloud CLI, otherwise SSH integration
# tests won't work.
if [[ -z "$(gcloud auth print-identity-token)" ]]; then
echo "gcloud CLI authentication missing. Press enter to continue..."
read var
gcloud auth login
fi
echo "✅ Your machine is bootstrapped and ready to go. :)"
+277
View File
@@ -0,0 +1,277 @@
#!/usr/bin/env bash
#
# Builds a Warp binary and bundles it up for distribution.
set -e
WORKSPACE_ROOT_DIR="$(pwd)"
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT_DIR/target}"
DIST_DIR="$CARGO_TARGET_DIR/dist"
cleanup() {
# Delete the directory that was used as a staging area during the bundling
# process.
if [ -d "$DIST_DIR" ]; then
rm -rf "$DIST_DIR"
fi
}
# Run cleanup() when the script terminates (whether it succeeded or failed).
trap cleanup EXIT
# By default we build dev bundles.
RELEASE_CHANNEL="dev"
FEATURES="release_bundle,crash_reporting"
PACKAGES=( appimage )
BUILD="true"
BUILD_ARCH="$(uname -m)"
DEBUG=false
ARTIFACT="app"
# Cache all params so we can pass them to downstream scripts.
ALL_PARAMS=$@
PARAMS=""
while (( "$#" )); do
case "$1" in
--debug)
DEBUG=true
shift
;;
--check-only)
echo 'Only running `cargo check` and not producing a bundle.'
CHECK_ONLY="true"
shift
;;
--skip-build)
BUILD="false"
shift
;;
--nouniversal)
# Discard the --nouniversal argument, which is only used for macOS
# bundles.
shift
;;
-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
;;
--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
;;
--packages)
PACKAGES=( $(IFS=, ; echo $2) )
shift 2
;;
--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
;;
--arch)
if [ -n "$2" ]; then
if [ "$2" = "aarch64" -o "$2" = "x86_64" ]; then
echo "Setting architecture to $2"
export BUILD_ARCH=$2
shift 2
else
echo "Error: Argument for $1 is invalid; got '$2' but expected 'aarch64' or 'x86_64'." >&2
exit 1
fi
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"
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
if [[ "$CARGO_PROFILE" == "dev" ]]; then
CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_DIR/debug"
else
CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_DIR/$CARGO_PROFILE"
fi
# NOTE: if you change this path, update the "Clean stale bundle output"
# steps in .github/workflows/create_release.yml so they continue to wipe
# stale packages left in the shared Namespace runner cache.
OUT_DIR="$CARGO_TARGET_OUTPUT_DIR/bundle/linux"
mkdir -p "$OUT_DIR"
# Update parameters based on the target release channel.
#
# APP_NAME here must match the value used in Rust as the
# application name; see app/src/channel.rs.
#
# WARP_BIN is the name of the binary produced by cargo;
# BINARY_NAME is the desired name of the binary in the final package.
if [[ $RELEASE_CHANNEL = "local" ]]; then
WARP_BIN="warp"
BINARY_NAME="warp-local"
APP_NAME="WarpLocal"
FEATURES="$FEATURES,agent_mode_debug"
export HANDLE_MARKDOWN=1
elif [[ $RELEASE_CHANNEL = "dev" ]]; then
WARP_BIN="dev"
BINARY_NAME="warp-dev"
APP_NAME="WarpDev"
FEATURES="$FEATURES,agent_mode_debug"
# Enable heap profiling using jemalloc through pprof.
FEATURES="$FEATURES,jemalloc_pprof"
export HANDLE_MARKDOWN=1
elif [[ $RELEASE_CHANNEL = "preview" ]]; then
WARP_BIN="preview"
BINARY_NAME="warp-preview"
APP_NAME="WarpPreview"
FEATURES="$FEATURES,preview_channel"
elif [[ $RELEASE_CHANNEL = "stable" ]]; then
WARP_BIN="stable"
BINARY_NAME="warp"
APP_NAME="Warp"
fi
# Artifact-specific binary naming
if [[ "$ARTIFACT" == "cli" ]]; then
# For CLI artifacts, use oz instead of warp as the binary name
BINARY_NAME="${BINARY_NAME/warp/oz}"
fi
# Artifact-specific configuration
if [[ "$ARTIFACT" == "cli" ]]; then
FEATURES="$FEATURES,standalone"
elif [[ "$ARTIFACT" == "app" ]]; then
FEATURES="$FEATURES,gui,nld_improvements"
fi
BUNDLE_ID="dev.warp.$APP_NAME"
EXECUTABLE_PATH="$CARGO_TARGET_OUTPUT_DIR/$WARP_BIN"
DEBUG_EXECUTABLE_PATH="$EXECUTABLE_PATH.debug"
# Note that this variable must be set (and exported!) before we compile the
# binary, as it is read at compile time by Linux autoupdate logic (to know the
# expected name of the AppImage when downloading updates).
export APPIMAGE_NAME="$APP_NAME-$BUILD_ARCH.AppImage"
# 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 -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES"
exit 0
fi
# Build the binary.
if [[ "$BUILD" == "true" ]]; then
echo "Building and bundling Warp for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID"
cargo build -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES"
echo "Making debug copy of '$EXECUTABLE_PATH' at '$DEBUG_EXECUTABLE_PATH'"
cp "$EXECUTABLE_PATH" "$DEBUG_EXECUTABLE_PATH"
echo "Stripping debug symbols from '$EXECUTABLE_PATH'"
if [[ $RELEASE_CHANNEL = "dev" ]]; then
# For dev builds, only strip debug symbols, as we want to keep some
# symbols for profiling purposes.
strip --strip-debug "$EXECUTABLE_PATH"
else
# For production builds, strip all symbols, to keep the binary size
# smaller.
strip --strip-all "$EXECUTABLE_PATH"
fi
else
echo 'Skipping `cargo build` step due to --skip-build argument'
fi
# Prepare bundled resources for CLI builds.
if [[ "$ARTIFACT" == "cli" ]]; then
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"
fi
# If this is being run within a GitHub action, set an output variable with the
# location of the binary so it can be referenced by subsequent actions, as well
# as the directory containing all built packages.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "::echo::on"
echo "executable_path=$EXECUTABLE_PATH" >> "$GITHUB_OUTPUT"
echo "debug_executable_path=$DEBUG_EXECUTABLE_PATH" >> "$GITHUB_OUTPUT"
echo "packages_dir=$OUT_DIR" >> "$GITHUB_OUTPUT"
echo "bundled_resources_dir=${BUNDLED_RESOURCES_DIR:-}" >> "$GITHUB_OUTPUT"
echo "::echo::off"
fi
# Make sure a variety of environment variables are available in downstream scripts.
export \
WORKSPACE_ROOT_DIR \
DIST_DIR \
CARGO_TARGET_OUTPUT_DIR \
OUT_DIR \
RELEASE_CHANNEL \
BUNDLE_ID \
EXECUTABLE_PATH \
BINARY_NAME \
APPIMAGE_NAME \
BUILD_ARCH \
ARTIFACT \
CARGO_PROFILE
# Build the AppImage bundle.
if [[ ${PACKAGES[@]} =~ "appimage" ]]; then
echo "Building AppImage..."
"$WORKSPACE_ROOT_DIR/script/linux/bundle_appimage"
fi
# Build the .deb package.
if [[ ${PACKAGES[@]} =~ "deb" ]]; then
echo "Building .deb package..."
"$WORKSPACE_ROOT_DIR/script/linux/bundle_deb"
fi
# Build the .rpm package.
if [[ ${PACKAGES[@]} =~ "rpm" ]]; then
echo "Building .rpm package..."
"$WORKSPACE_ROOT_DIR/script/linux/bundle_rpm"
fi
# Build the Arch Linux package.
if [[ ${PACKAGES[@]} =~ "arch" ]]; then
echo "Building Arch Linux package..."
"$WORKSPACE_ROOT_DIR/script/linux/bundle_arch"
fi
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
#
# Builds an AppImage as part of the bundling process.
#
# Required environment variables:
# - WORKSPACE_ROOT_DIR: The root directory of the Cargo workspace.
# - DIST_DIR: A temporary directory we can use for staging files as we build the package.
# - OUT_DIR: The directory into which we should place the final package.
# - CARGO_TARGET_OUTPUT_DIR: The cargo target directory containing build output.
# - RELEASE_CHANNEL: The release channel we're bundling (e.g.: "dev").
# - BUNDLE_ID: The app ID for the bundle (e.g.: "dev.warp.WarpDev").
# - EXECUTABLE_PATH: The path to the compiled executable we want to install.
# - BINARY_NAME: The name that the compiled executable should have on the target machine.
# - APPIMAGE_NAME: The name of the AppImage file to produce.
STAGE_DIR="$DIST_DIR/appimagepkg"
APP_DIR="$DIST_DIR/AppDir"
# Extract channel suffix from BINARY_NAME
if [ "$ARTIFACT" = "cli" ]; then
CHANNEL_SUFFIX="${BINARY_NAME#oz}"
else
CHANNEL_SUFFIX="${BINARY_NAME#warp}"
fi
PACKAGE_NAME="warp-terminal$CHANNEL_SUFFIX"
# Adding NO_STRIP to stop linuxdeploy from attempting to strip symbols
# from binaries built on a different arch than the current symbol.
BUILD_ARCH="${BUILD_ARCH:-$(uname -m)}"
if [ "$BUILD_ARCH" != "$(uname -m)" ]; then
export NO_STRIP=1
fi
cleanup() {
# Delete the directories used as staging areas during the bundling
# process.
if [ -d "$STAGE_DIR" ]; then
rm -rf "$STAGE_DIR"
fi
if [ -d "$APP_DIR" ]; then
rm -rf "$APP_DIR"
fi
}
# Run cleanup() when the script terminates (whether it succeeded or failed).
trap cleanup EXIT
# Construct a directory to contain the staged package contents.
rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR"
# Populate the directory with the package contents, in the appropriate
# locations.
OPT_DIR="/opt/warpdotdev/$PACKAGE_NAME"
source "$WORKSPACE_ROOT_DIR/script/linux/bundle_install" "$STAGE_DIR"
# Modify the "Exec=" line in the .desktop file to use the name of the installed
# binary and not our /usr/bin symlink (which doesn't exist in an AppImage).
sed -i -E 's/Exec=warp-terminal/Exec=warp/' "$STAGE_DIR/usr/share/applications/$BUNDLE_ID.desktop"
# Find all icon files from inside the package staging directory.
ICON_FILES=( $(find "$STAGE_DIR/usr/share/icons" -name "*.png") )
# Make sure there's no lingering AppDir from a previous execution.
rm -rf "$APP_DIR"
# Run linuxdeploy to create an appropriately-structured AppDir and turn it into
# an AppImage, located in OUT_DIR.
#
# We use a custom input plugin (bundled-resources) to copy Warp's bundled
# resources into the AppDir alongside the executable, since linuxdeploy only
# deploys the binary, desktop file, and icons by default.
cd "$OUT_DIR"
echo "Running linuxdeploy to create the AppImage"
export WARP_BINARY_NAME="$BINARY_NAME"
export WARP_PACKAGE_NAME="$PACKAGE_NAME"
export WARP_BUNDLED_RESOURCES_DIR="${STAGE_DIR}${OPT_DIR}/resources"
export PATH="$WORKSPACE_ROOT_DIR/script/linux:$PATH"
linuxdeploy \
--appdir "$APP_DIR" \
--executable "${STAGE_DIR}${OPT_DIR}/$BINARY_NAME" \
--desktop-file "$STAGE_DIR/usr/share/applications/$BUNDLE_ID.desktop" \
$(for i in "${ICON_FILES[@]}"; do echo "--icon-file $i "; done) \
--plugin warp \
--output appimage
APPIMAGE_PATH="$OUT_DIR/$APPIMAGE_NAME"
# If this is being run within a GitHub action, set an output variable with the
# location of the AppImage so it can be referenced by subsequent actions.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "::echo::on"
echo "appimage_path=$APPIMAGE_PATH" >> "$GITHUB_OUTPUT"
echo "::echo::off"
fi
echo "Successfully built AppImage at $APPIMAGE_PATH!"
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
#
# Builds an Arch Linux package as part of the bundling process.
#
# Required environment variables:
# - WORKSPACE_ROOT_DIR: The root directory of the Cargo workspace.
# - DIST_DIR: A temporary directory we can use for staging files as we build the package.
# - OUT_DIR: The directory into which we should place the final package.
# - CARGO_TARGET_OUTPUT_DIR: The cargo target directory containing build output.
# - RELEASE_CHANNEL: The release channel we're bundling (e.g.: "dev").
# - BUNDLE_ID: The app ID for the bundle (e.g.: "dev.warp.WarpDev").
# - EXECUTABLE_PATH: The path to the compiled executable we want to install.
# - BINARY_NAME: The name that the compiled executable should have on the target machine.
# - ARTIFACT: Which artifact to build: app or cli.
set -e
# Extract channel suffix from BINARY_NAME
if [ "$ARTIFACT" = "cli" ]; then
CHANNEL_SUFFIX="${BINARY_NAME#oz}"
else
CHANNEL_SUFFIX="${BINARY_NAME#warp}"
fi
# On Linux, we keep the `-stable` suffix for stable to avoid package conflicts.
# The binary name is still `oz`, and the repo name is still `warpdotdev`.
if [ "$ARTIFACT" = "cli" -a "$RELEASE_CHANNEL" = "stable" ]; then
CHANNEL_SUFFIX="-stable"
fi
if [ "$ARTIFACT" = "app" ]; then
PACKAGE_NAME="warp-terminal$CHANNEL_SUFFIX"
elif [ "$ARTIFACT" = "cli" ]; then
PACKAGE_NAME="oz$CHANNEL_SUFFIX"
else
echo "::error ::Unknown ARTIFACT: $ARTIFACT (expected 'app' or 'cli')"
exit 1
fi
BUILD_ARCH="${BUILD_ARCH:-$(uname -m)}"
if [ "$BUILD_ARCH" = "aarch64" ]; then
ARCH="aarch64"
else
ARCH="x86_64"
fi
PKGDIR="$DIST_DIR/archpkg"
cleanup() {
# Delete the AppDir that was used as a staging area during the bundling
# process.
if [ -d "$PKGDIR" ]; then
rm -rf "$PKGDIR"
fi
}
# Run cleanup() when the script terminates (whether it succeeded or failed).
trap cleanup EXIT
# Construct a directory that contains all of the output data.
rm -rf "$PKGDIR"
mkdir -p "$PKGDIR"
# Bundle the base filesystem into an archive to be unpacked by makepkg.
mkdir "$PKGDIR/stage"
OPT_DIR="/opt/warpdotdev/$PACKAGE_NAME"
source "$WORKSPACE_ROOT_DIR/script/linux/bundle_install" "$PKGDIR/stage"
tar cvf "$PKGDIR/data.tar" -C "$PKGDIR/stage" .
# Move the Arch-specific package metadata into the package directory.
VERSION="$GIT_RELEASE_TAG"
RELEASE=1
sed \
"s#@@CHANNEL_SUFFIX@@#$CHANNEL_SUFFIX#g; s#@@VERSION@@#$VERSION#g; s#@@RELEASE@@#$RELEASE#g; s#@@ARCH@@#$ARCH#g; s#@@OUTDIR@@#$OUT_DIR#g; s#@@BINARY_NAME@@#$BINARY_NAME#g" \
< "$WORKSPACE_ROOT_DIR/resources/linux/arch/$ARTIFACT/PKGBUILD.template" \
> "$PKGDIR/PKGBUILD"
# Only create wrapper script for app artifact
if [ "$ARTIFACT" = "app" ]; then
sed \
"s#@@CHANNEL_SUFFIX@@#$CHANNEL_SUFFIX#g; s#@@BINARY_NAME@@#$BINARY_NAME#g" \
< "$WORKSPACE_ROOT_DIR/resources/linux/arch/$ARTIFACT/warp.sh.template" \
> "$PKGDIR/$PACKAGE_NAME.sh"
fi
# Build the actual package.
#
# We install any needed build and runtime dependencies before making the
# package, and remove them again afterwards. We also skip verifying
# checksums for the source files, as we generate them above and there's not
# much reason to generate the checksums and then verify them immediately
# afterwards.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
# If we're running in a GitHub action, don't ask for confirmation before
# installing packages.
EXTRA_ARGS="--noconfirm"
fi
cd "$PKGDIR"
PKGDEST="$OUT_DIR" CARCH="$ARCH" makepkg -cfrs --needed --skipchecksums "$EXTRA_ARGS"
PACKAGE_PATH="$OUT_DIR/${PACKAGE_NAME}-${VERSION}-${RELEASE}-${ARCH}.pkg.tar.zst"
echo "Successfully built Arch package at $PACKAGE_PATH!"
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
#
# Builds a .deb package as part of the bundling process.
#
# Required environment variables:
# - WORKSPACE_ROOT_DIR: The root directory of the Cargo workspace.
# - DIST_DIR: A temporary directory we can use for staging files as we build the package.
# - OUT_DIR: The directory into which we should place the final package.
# - CARGO_TARGET_OUTPUT_DIR: The cargo target directory containing build output.
# - RELEASE_CHANNEL: The release channel we're bundling (e.g.: "dev").
# - BUNDLE_ID: The app ID for the bundle (e.g.: "dev.warp.WarpDev").
# - EXECUTABLE_PATH: The path to the compiled executable we want to install.
# - BINARY_NAME: The name that the compiled executable should have on the target machine.
# - ARTIFACT: Which artifact to build: app or cli.
set -e
# Extract channel suffix from BINARY_NAME
if [ "$ARTIFACT" = "cli" ]; then
CHANNEL_SUFFIX="${BINARY_NAME#oz}"
else
CHANNEL_SUFFIX="${BINARY_NAME#warp}"
fi
REPO_NAME="warpdotdev$CHANNEL_SUFFIX"
# On Linux, we keep the `-stable` suffix for stable to avoid package conflicts.
# The binary name is still `oz`, and the repo name is still `warpdotdev`.
if [ "$ARTIFACT" = "cli" -a "$RELEASE_CHANNEL" = "stable" ]; then
CHANNEL_SUFFIX="-stable"
fi
case "$ARTIFACT" in
app)
PACKAGE_NAME="warp-terminal$CHANNEL_SUFFIX"
;;
cli)
PACKAGE_NAME="oz$CHANNEL_SUFFIX"
;;
*)
echo "::error ::Unknown ARTIFACT: $ARTIFACT (expected 'app' or 'cli')"
exit 1
;;
esac
TEMPLATE_DIR="$WORKSPACE_ROOT_DIR/resources/linux/debian/$ARTIFACT"
# Add a simple test to make sure we're generating the appropriate repository
# name for the stable channel.
if [ "$RELEASE_CHANNEL" = "stable" ]; then
if [ "$REPO_NAME" != "warpdotdev" ]; then
echo "::error ::Unexpected repo name for $RELEASE_CHANNEL channel: $REPO_NAME (expected \"warpdotdev\")"
exit 1
fi
fi
BUILD_ARCH="${BUILD_ARCH:-$(uname -m)}"
if [ "$BUILD_ARCH" = "aarch64" ]; then
ARCH="arm64"
else
ARCH="amd64"
fi
FULL_PACKAGE_NAME="$PACKAGE_NAME-$ARCH"
PKGDIR="$DIST_DIR/debpkg/$FULL_PACKAGE_NAME"
cleanup() {
# Delete the AppDir that was used as a staging area during the bundling
# process.
if [ -d "$PKGDIR" ]; then
rm -rf "$PKGDIR"
fi
}
# Run cleanup() when the script terminates (whether it succeeded or failed).
trap cleanup EXIT
# Construct a directory that contains all of the output data.
rm -rf "$PKGDIR"
mkdir -p "$PKGDIR"
# Populate the directory with the package contents, in the appropriate
# locations.
OPT_DIR="/opt/warpdotdev/$PACKAGE_NAME"
source "$WORKSPACE_ROOT_DIR/script/linux/bundle_install" "$PKGDIR"
# Move the Debian-specific package metadata into the package directory.
DEBIAN_DIR="$PKGDIR/DEBIAN"
VERSION="$(echo "$GIT_RELEASE_TAG" | sed -E "s/^v//; s/([a-z]+)_/\1./")"
mkdir "$DEBIAN_DIR"
sed \
"s#@@CHANNEL_SUFFIX@@#$CHANNEL_SUFFIX#g; s#@@VERSION@@#$VERSION#g; s#@@ARCH@@#$ARCH#g" \
< "$TEMPLATE_DIR/control.template" \
> "$DEBIAN_DIR/control"
sed \
"s#@@CHANNEL_SUFFIX@@#$CHANNEL_SUFFIX#g; s#@@BINARY_NAME@@#$BINARY_NAME#g; s#@@OPTDIR@@#$OPT_DIR#g; s#@@REPO_NAME@@#$REPO_NAME#g; s#@@CHANNEL@@#$RELEASE_CHANNEL#g; s#@@ARCH@@#$ARCH#g" \
< "$TEMPLATE_DIR/postinst.template" \
> "$DEBIAN_DIR/postinst"
sed \
"s#@@REPO_NAME@@#$REPO_NAME#g; s#@@CHANNEL@@#$RELEASE_CHANNEL#g; s#@@ARCH@@#$ARCH#g" \
< "$WORKSPACE_ROOT_DIR/resources/linux/debian/common/postinst.repo.template" \
>> "$DEBIAN_DIR/postinst"
sed \
"s#@@CHANNEL_SUFFIX@@#$CHANNEL_SUFFIX#g; s#@@OPTDIR@@#$OPT_DIR#g; s#@@REPO_NAME@@#$REPO_NAME#g" \
< "$TEMPLATE_DIR/postrm.template" \
> "$DEBIAN_DIR/postrm"
sed \
"s#@@REPO_NAME@@#$REPO_NAME#g" \
< "$WORKSPACE_ROOT_DIR/resources/linux/debian/common/postrm.repo.template" \
>> "$DEBIAN_DIR/postrm"
# Make the package scripts executable.
chmod 755 \
"$DEBIAN_DIR/postinst" \
"$DEBIAN_DIR/postrm"
# Build the actual package.
cd "$DIST_DIR/debpkg"
fakeroot dpkg-deb -b "$PACKAGE_NAME-$ARCH" "$OUT_DIR"
echo "Successfully built .deb package at $OUT_DIR/${PACKAGE_NAME}_${VERSION}_${ARCH}.deb!"
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
#
# Installs everything built by the bundle script into the given directory.
#
# This is intended to be used when building Linux packages (e.g.: .deb).
#
# Required environment variables:
# - WORKSPACE_ROOT_DIR: The root directory of the Cargo workspace.
# - CARGO_TARGET_OUTPUT_DIR: The cargo target directory containing build output.
# - OPT_DIR: The absolute path to our install directory (under /opt) on the target machine.
# - RELEASE_CHANNEL: The release channel we're bundling (e.g.: "dev").
# - BUNDLE_ID: The app ID for the bundle (e.g.: "dev.warp.WarpDev").
# - EXECUTABLE_PATH: The path to the compiled executable we want to install.
# - BINARY_NAME: The name that the compiled executable should have on the target machine.
# - ARTIFACT: Which artifact to build: app or cli.
set -e
# The target directory should be the first (and only) positional argument.
if [[ $# -gt 1 ]]; then
echo "Expected 1 argument but received $#!"
exit 1
fi
TARGET_DIR="$1"
# Make sure the target directory is empty.
if [[ ! -z "$(ls -A "$TARGET_DIR")" ]]; then
echo "Target directory '$TARGET_DIR' not empty!"
exit 1
fi
# Copy files to the desired output locations within the target dir, stripping
# debugging symbols from the installed binaries.
install -D "$EXECUTABLE_PATH" "${TARGET_DIR}${OPT_DIR}/$BINARY_NAME"
# Only install desktop file and icons if the artifact is "app"
if [[ "$ARTIFACT" == "app" ]]; then
install -Dm644 "$WORKSPACE_ROOT_DIR/app/channels/$RELEASE_CHANNEL/$BUNDLE_ID.desktop" "${TARGET_DIR}/usr/share/applications/$BUNDLE_ID.desktop"
for size in 16x16 32x32 64x64 128x128 256x256 512x512; do
src_path="$WORKSPACE_ROOT_DIR/app/channels/$RELEASE_CHANNEL/icon/no-padding/$size.png"
if [[ -f "$src_path" ]]; then
install -Dm644 "$src_path" "${TARGET_DIR}/usr/share/icons/hicolor/$size/apps/$BUNDLE_ID.png"
fi
done
fi
# Prepare the bundled resources directory.
"$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "${TARGET_DIR}/${OPT_DIR}/resources" "$RELEASE_CHANNEL" "$CARGO_PROFILE"
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
#
# Builds a .rpm package as part of the bundling process.
#
# Required environment variables:
# - WORKSPACE_ROOT_DIR: The root directory of the Cargo workspace.
# - DIST_DIR: A temporary directory we can use for staging files as we build the package.
# - OUT_DIR: The directory into which we should place the final package.
# - CARGO_TARGET_OUTPUT_DIR: The cargo target directory containing build output.
# - RELEASE_CHANNEL: The release channel we're bundling (e.g.: "dev").
# - BUNDLE_ID: The app ID for the bundle (e.g.: "dev.warp.WarpDev").
# - EXECUTABLE_PATH: The path to the compiled executable we want to install.
# - BINARY_NAME: The name that the compiled executable should have on the target machine.
# - ARTIFACT: Which artifact to build: app or cli.
set -e
# Extract channel suffix from BINARY_NAME
if [ "$ARTIFACT" = "cli" ]; then
CHANNEL_SUFFIX="${BINARY_NAME#oz}"
else
CHANNEL_SUFFIX="${BINARY_NAME#warp}"
fi
REPO_NAME="warpdotdev$CHANNEL_SUFFIX"
# On Linux, we keep the `-stable` suffix for stable to avoid package conflicts.
# The binary name is still `oz`, and the repo name is still `warpdotdev`.
if [ "$ARTIFACT" = "cli" -a "$RELEASE_CHANNEL" = "stable" ]; then
CHANNEL_SUFFIX="-stable"
fi
ARTIFACT="${ARTIFACT:-app}"
if [ "$ARTIFACT" = "app" ]; then
PACKAGE_NAME="warp-terminal$CHANNEL_SUFFIX"
elif [ "$ARTIFACT" = "cli" ]; then
PACKAGE_NAME="oz$CHANNEL_SUFFIX"
else
echo "::error ::Unknown ARTIFACT: $ARTIFACT (expected 'app' or 'cli')"
exit 1
fi
BUILD_ARCH="${BUILD_ARCH:-$(uname -m)}"
if [ "$BUILD_ARCH" = "aarch64" ]; then
ARCH="aarch64"
else
ARCH="x86_64"
fi
RPMBUILD_DIR="$DIST_DIR/rpmbuild"
FULL_PACKAGE_NAME="$PACKAGE_NAME-$ARCH"
PKGDIR="$DIST_DIR/rpmpkg/$FULL_PACKAGE_NAME"
cleanup() {
# Delete the rpmbuild directory where the build took place.
if [ -d "$RPMBUILD_DIR" ]; then
rm -rf "$RPMBUILD_DIR"
fi
}
# Run cleanup() when the script terminates (whether it succeeded or failed).
trap cleanup EXIT
# Construct a directory that contains the build tree.
rm -rf "$RPMBUILD_DIR"
mkdir -p "$RPMBUILD_DIR"/{SPEC,RPMS}
# Move the RPM-specific package metadata into the package directory.
VERSION="$GIT_RELEASE_TAG"
RELEASE="1"
sed \
"s#@@CHANNEL_SUFFIX@@#$CHANNEL_SUFFIX#g; \
s#@@BINARY_NAME@@#$BINARY_NAME#g; \
s#@@VERSION@@#$VERSION#g; \
s#@@RELEASE@@#$RELEASE#g; \
s#@@ARCH@@#$ARCH#g; \
s#@@WORKSPACEROOT@@#$WORKSPACE_ROOT_DIR#g; \
s#@@BUNDLEID@@#$BUNDLE_ID#g; \
s#@@REPO_NAME@@#$REPO_NAME#g; \
s#@@RELEASE_CHANNEL@@#$RELEASE_CHANNEL#g" \
< "$WORKSPACE_ROOT_DIR/resources/linux/rpm/$ARTIFACT/warp.spec.template" \
> "$RPMBUILD_DIR/SPEC/warp.spec"
# Build the actual package.
fakeroot rpmbuild -v -bb "$RPMBUILD_DIR/SPEC/warp.spec" --target="$ARCH" --define "_topdir $RPMBUILD_DIR"
# Copy the package to the output directory.
RPM_NAME="$PACKAGE_NAME-$VERSION-$RELEASE.$ARCH.rpm"
cp "$RPMBUILD_DIR/RPMS/$ARCH/$RPM_NAME" "$OUT_DIR"
# If we're running on GitHub, sign the package using our signing key that
# should have already been added to the gpg-agent with a preset passphrase.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
# Import our signing key's public key into rpm so the signature can be
# verified.
sudo rpm --import https://releases.warp.dev/linux/keys/warp.asc
# We use batch mode with tty-based pin entry to ensure that the build doesn't
# hang waiting on user input if, for some reason, we failed to preset the
# passphrase for the signing key in the gpg-agent cache.
#
# Additionally, until we know that this works, don't let an error here fail
# the build.
rpmsign \
--verbose \
--addsign --key-id "linux-maintainers@warp.dev" \
--define "_gpg_sign_cmd_extra_args --batch --yes --pinentry-mode loopback" --define "_gpg_digest_algo sha256" \
"$OUT_DIR/$RPM_NAME" \
|| true
fi
echo "Successfully built .rpm package at $OUT_DIR/$RPM_NAME!"
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
#
# Install all dependencies required to build Warp on Linux.
set -e
# Define some control sequences for text formatting.
red="\033[0;31m"
reset="\033[0m"
# Install some development libraries that will be needed in the compilation
# process.
UNAME="$(uname -a)"
if [[ "$(source /etc/os-release; echo $ID $ID_LIKE)" = *"debian"* ]]; then
echo "⬇️ Installing build-time dependencies..."
# Define the packages to install as a bash array so we can include
# comments between lines.
PACKAGES=(
curl
git
# Install various core packages for development work on Linux.
build-essential cmake pkg-config
# Make it so that running `python` runs `python3`.
python-is-python3
# Development headers for various libraries, needed when compiling
# certain Rust crate dependencies.
libssl-dev libfreetype-dev libexpat1-dev libgit2-dev
# libs needed for loading system fonts
libfontconfig1-dev
# Needed in wasm compilation for parsing the version of wasm-bindgen
jq
# Needed for compressing web bundles
brotli
# Needed for voice input
libasound2-dev
)
sudo apt-get install -y "${PACKAGES[@]}"
# Install a modern version of protoc. The apt 'protobuf-compiler' package on
# Ubuntu 20.04 ships protoc 3.6.1, which is too old for proto3 'optional'
# fields (requires protoc >= 3.15).
PROTOC_VERSION="25.1"
case "$(uname -m)" in
x86_64) PROTOC_ZIP="protoc-${PROTOC_VERSION}-linux-x86_64.zip" ;;
aarch64) PROTOC_ZIP="protoc-${PROTOC_VERSION}-linux-aarch_64.zip" ;;
*) echo "Unsupported architecture for protoc: $(uname -m)"; exit 1 ;;
esac
curl -fsSL -o /tmp/protoc.zip \
"https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP}"
sudo unzip -o /tmp/protoc.zip -d /usr/local bin/protoc 'include/*'
rm /tmp/protoc.zip
else
echo -e "⚠️ ${red}Unknown Linux distribution; necessary build dependencies may not be installed!${reset}"
fi
# Install Rust.
"$PWD"/script/install_rust
# Install various build-time dependencies through cargo.
"$PWD"/script/install_cargo_build_deps
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -e
# Ensure the ~/.local/bin directory exists so we can download things into it.
LOCAL_BIN="$HOME/.local/bin"
mkdir -p "$LOCAL_BIN"
# A helper function to download an architecture-dependent AppImage and install
# it in ~/.local/bin.
#
# Usage:
# download_appimage $BINARY_NAME $DOWNLOAD_URL_BASE
#
# Args:
# BINARY_NAME: The name of the AppImage-bundled binary we want to
# download, e.g.: "appimagetool".
# DOWNLOAD_URL_BASE: The URL to the directory containing the architecture-
# specific AppImages.
download_appimage() {
BINARY_NAME="$1"
DOWNLOAD_URL_BASE="$2"
APPIMAGE_NAME="$BINARY_NAME-$(uname -m).AppImage"
if [ -x "$(which "$BINARY_NAME")" ]; then
echo "✅ Found $BINARY_NAME on your PATH."
elif [ -x "$(which "$APPIMAGE_NAME")" ]; then
APPIMAGE_PATH="$(which "$APPIMAGE_NAME")"
APPIMAGE_PARENT_DIR="$(dirname "$APPIMAGE_PATH")"
# Create a symlink so we can invoke it like a normal binary.
ln -s "$APPIMAGE_PATH" "$APPIMAGE_PARENT_DIR/$BINARY_NAME"
echo "✅ Found $APPIMAGE_NAME on your PATH; added $BINARY_NAME symlink."
else
echo "⬇️ Downloading $APPIMAGE_NAME..."
APPIMAGE_PATH="$LOCAL_BIN/$APPIMAGE_NAME"
curl -fL "$DOWNLOAD_URL_BASE/$APPIMAGE_NAME" --output "$APPIMAGE_PATH"
chmod +x "$APPIMAGE_PATH"
# Create a symlink so we can invoke it like a normal binary.
ln -s "$APPIMAGE_PATH" "$LOCAL_BIN/$BINARY_NAME"
fi
}
# Install linuxdeploy, a helper for constructing an AppDir (and then invoking
# appimagetool).
download_appimage "linuxdeploy" "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20240109-1/"
if [ ! -x "$(which linuxdeploy)" ]; then
echo -e "⚠️ ${red}Please make sure that \"$LOCAL_BIN\" is on your PATH, otherwise some scripts may not work.${reset}"
fi
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
#
# Install all dependencies required to build and run Warp on Linux.
set -e
# Define some control sequences for text formatting.
red="\033[0;31m"
reset="\033[0m"
# Install build dependencies.
"$PWD"/script/linux/install_build_deps
# Install additional runtime dependencies.
UNAME="$(uname -a)"
if [[ "$(source /etc/os-release; echo $ID $ID_LIKE)" = *"debian"* ]]; then
echo "⬇️ Installing runtime dependencies..."
# Define the packages to install as a bash array so we can include
# comments between lines.
PACKAGES=(
locales
# Runtime library to get font information.
fontconfig
# Runtime library for deflate compression.
zlib1g
# Runtime libraries for X11.
libx11-6 libxcb1 libxi6 libxcursor1 libxkbcommon-x11-0
# Runtime libraries for Wayland.
libwayland-client0 libwayland-egl1
# Open-source Vulkan drivers from the mesa project.
mesa-vulkan-drivers
# Runtime libraries for EGL.
libegl1
# Ensuring at least one cursor library for WSL.
yaru-theme-icon
)
sudo apt-get install -y "${PACKAGES[@]}"
else
echo -e "⚠️ ${red}Unknown Linux distribution; necessary runtime dependencies may not be installed!${reset}"
fi
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
#
# Install all dependencies required to build, run, and test Warp on Linux.
set -e
# Define some control sequences for text formatting.
red="\033[0;31m"
reset="\033[0m"
# Install runtime dependencies.
"$PWD"/script/linux/install_runtime_deps
# Install additional testing dependencies.
UNAME="$(uname -a)"
if [[ "$(source /etc/os-release; echo $ID $ID_LIKE)" = *"debian"* ]]; then
echo "⬇️ Installing test dependencies..."
# Define the packages to install as a bash array so we can include
# comments between lines.
PACKAGES=(
# Install the zsh and fish shells.
zsh fish
# We run vim in some integration tests to test the altscreen.
vim
)
sudo apt-get install -y "${PACKAGES[@]}"
# If gcloud is not already installed, install it so that we can run SSH integration tests.
if [[ ! -x "$(command -v gcloud)" ]]; then
echo "⬇️ Installing the gcloud CLI..."
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
curl -f https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
sudo apt-get update -y
sudo apt-get install google-cloud-cli -y
fi
else
echo -e "⚠️ ${red}Unknown Linux distribution; necessary test dependencies may not be installed!${reset}"
fi
# Install various testing dependencies through cargo.
"$PWD"/script/install_cargo_test_deps
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
#
# linuxdeploy input plugin for restructuring the AppDir to match Warp's
# standard Linux install layout.
#
# By default, linuxdeploy places the executable at usr/bin/ inside the AppDir.
# This plugin moves it to opt/warpdotdev/<package>/ (matching deb/rpm/arch
# package layout), creates a symlink from usr/bin/ to the new location, and
# copies bundled resources alongside the binary.
#
# This means that extracting an AppImage produces the same filesystem layout
# as installing a .deb or .rpm package.
#
# Required environment variables:
# WARP_BINARY_NAME: Name of the binary (e.g. "warp-dev").
# WARP_PACKAGE_NAME: Package directory name (e.g. "warp-terminal-dev").
# WARP_BUNDLED_RESOURCES_DIR: Path to the staged resources directory to copy.
set -e
while [[ "$#" -gt 0 ]]; do
case "$1" in
--plugin-api-version)
echo "0"
exit 0
;;
--appdir)
APPDIR="$2"
shift 2
;;
*)
shift
;;
esac
done
if [ -z "$APPDIR" ]; then
echo "ERROR: --appdir is required" >&2
exit 1
fi
for var in WARP_BINARY_NAME WARP_PACKAGE_NAME WARP_BUNDLED_RESOURCES_DIR; do
if [ -z "${!var}" ]; then
echo "ERROR: $var environment variable is not set" >&2
exit 1
fi
done
if [ ! -d "$WARP_BUNDLED_RESOURCES_DIR" ]; then
echo "ERROR: Bundled resources directory does not exist: $WARP_BUNDLED_RESOURCES_DIR" >&2
exit 1
fi
SRC_BIN="$APPDIR/usr/bin/$WARP_BINARY_NAME"
if [ ! -f "$SRC_BIN" ]; then
echo "ERROR: Expected binary not found at $SRC_BIN" >&2
exit 1
fi
# Create the /opt install directory inside the AppDir.
OPT_DIR="$APPDIR/opt/warpdotdev/$WARP_PACKAGE_NAME"
mkdir -p "$OPT_DIR"
# Move the binary from usr/bin/ to opt/warpdotdev/<package>/.
echo "Relocating binary to $OPT_DIR/$WARP_BINARY_NAME"
mv "$SRC_BIN" "$OPT_DIR/$WARP_BINARY_NAME"
# Create a relative symlink so usr/bin/<name> still resolves to the binary.
# From usr/bin/ to opt/warpdotdev/<package>/ is ../../opt/warpdotdev/<package>/.
echo "Creating symlink at usr/bin/$WARP_BINARY_NAME"
ln -s "../../opt/warpdotdev/$WARP_PACKAGE_NAME/$WARP_BINARY_NAME" "$SRC_BIN"
# Copy bundled resources alongside the binary.
DEST_RESOURCES="$OPT_DIR/resources"
echo "Copying bundled resources to $DEST_RESOURCES"
mkdir -p "$DEST_RESOURCES"
cp -R "$WARP_BUNDLED_RESOURCES_DIR/." "$DEST_RESOURCES/"
echo "Successfully restructured AppDir for Warp"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
#
# Signs the Arch Linux packages in a given directory.
PKGDIR="$1"
for package in $PKGDIR/*.pkg.tar.zst; do
gpg --no-tty --pinentry-mode loopback --detach-sign --no-armor --batch --yes --output $package.sig $package
done
+69
View File
@@ -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
+823
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
#
# Installs macOS-specific build dependencies required to build Warp.
xcodebuild -downloadComponent MetalToolchain
+142
View File
@@ -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
+237
View File
@@ -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"
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env -S fontforge -script
# Script that patches a given font to include the Warp logo as a glyph in the font,
# using a unicode codepoint in the private use area.
import os
import sys
import fontforge
import psMat
# Default values match what we used when initially patching Roboto.
SCRIPT_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
DEFAULT_SVG_PATH = os.path.join(SCRIPT_DIR, "warp.svg")
DEFAULT_CODEPOINT = 0xE500 # Private Use Area
DEFAULT_GLYPH_NAME = "warpLogo"
def usage(exit_code: int) -> None:
prog = os.path.basename(sys.argv[0])
print(
"\n".join(
[
f"Usage: {prog} <font.ttf> [--svg <icon.svg>] [--codepoint <hex>] [--name <glyph_name>]",
"",
"Patches the given font by importing an SVG icon as a new glyph.",
"Defaults:",
f" --svg {DEFAULT_SVG_PATH}",
f" --codepoint 0x{DEFAULT_CODEPOINT:04X}",
f" --name {DEFAULT_GLYPH_NAME}",
]
)
)
raise SystemExit(exit_code)
def parse_args(argv):
if len(argv) < 2 or argv[1] in ("-h", "--help"):
usage(0 if len(argv) >= 2 else 2)
font_path = argv[1]
svg_path = DEFAULT_SVG_PATH
codepoint = DEFAULT_CODEPOINT
glyph_name = DEFAULT_GLYPH_NAME
i = 2
while i < len(argv):
arg = argv[i]
if arg == "--svg":
i += 1
if i >= len(argv):
usage(2)
svg_path = argv[i]
elif arg == "--codepoint":
i += 1
if i >= len(argv):
usage(2)
v = argv[i]
# Accept 0xE500, E500, or decimal.
codepoint = int(v, 16) if (v.startswith("0x") or any(c in v.lower() for c in "abcdef")) else int(v)
elif arg == "--name":
i += 1
if i >= len(argv):
usage(2)
glyph_name = argv[i]
else:
print(f"Unknown argument: {arg}")
usage(2)
i += 1
return font_path, svg_path, codepoint, glyph_name
def patch_font(font_path: str, svg_path: str, codepoint: int, glyph_name: str) -> None:
if not os.path.exists(font_path):
raise SystemExit(f"Font not found: {font_path}")
if not os.path.exists(svg_path):
raise SystemExit(f"SVG not found: {svg_path}")
font = fontforge.open(font_path)
# Create or replace the glyph.
g = font.createChar(codepoint, glyph_name)
g.clear()
# Import the outlines.
g.importOutlines(svg_path)
# Clean up and make it robust for rasterization.
g.removeOverlap()
g.simplify()
g.correctDirection()
# Keep the logo from looking low: align the bottom of the imported outline to the baseline.
# (This is the same adjustment we ended up applying after the initial patch.)
xmin, ymin, xmax, ymax = g.boundingBox()
if ymin != 0:
g.transform(psMat.translate(0, -ymin))
g.round()
font.generate(font_path)
font.close()
def main():
font_path, svg_path, codepoint, glyph_name = parse_args(sys.argv)
patch_font(font_path, svg_path, codepoint, glyph_name)
print(f"Patched {font_path} with U+{codepoint:04X} ({glyph_name})")
if __name__ == "__main__":
main()
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
#
# Prepares bundled pprof for distribution.
#
# This script copies a pprof binary that should be bundled with Warp into a
# destination directory.
#
# Usage:
# prepare_bundled_pprof <destination_directory>
#
# Arguments:
# destination_directory: The directory where pprof should be installed.
set -e
if [ $# -ne 1 ]; then
echo "Error: Expected 1 argument (destination directory) but received $#" >&2
echo "Usage: $0 <destination_directory>" >&2
exit 1
fi
DEST_DIR="$1"
# Create the destination directory if it doesn't exist
mkdir -p "$DEST_DIR"
# Copy pprof
echo "Copying pprof to $DEST_DIR/pprof"
rm -f "$DEST_DIR/pprof"
cp "$(go tool -n pprof)" "$DEST_DIR/pprof"
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env bash
#
# Prepares bundled resources for distribution.
#
# This script copies resources that should be bundled with Warp into a
# destination directory. It is used by macOS and Linux build scripts.
#
# Usage:
# prepare_bundled_resources <destination_directory> [channel] [cargo_profile]
#
# Arguments:
# destination_directory: The directory where resources should be installed.
# Resources will be copied to subdirectories within
# this path (e.g., $DEST_DIR/skills).
# channel: (Optional) Release channel (local, dev, preview,
# stable). Used to include channel-gated skills.
# cargo_profile: (Optional) Cargo build profile to use when
# generating the settings schema. Reusing the same
# profile as the main build avoids recompiling deps.
#
# Environment variables:
# SKIP_SETTINGS_SCHEMA: Set to "1" to skip generating the JSON settings
# schema. By default the schema is always generated
# so that production bundles never accidentally omit it.
set -e
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
echo "Error: Expected 1-3 arguments but received $#" >&2
echo "Usage: $0 <destination_directory> [channel] [cargo_profile]" >&2
exit 1
fi
DEST_DIR="$1"
CHANNEL="${2:-}"
CARGO_PROFILE="${3:-}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
RESOURCES_SRC="$REPO_ROOT/resources"
# Validate that the source resources directory exists
if [ ! -d "$RESOURCES_SRC" ]; then
echo "Error: Resources directory not found at $RESOURCES_SRC" >&2
exit 1
fi
# Create the destination directory if it doesn't exist
mkdir -p "$DEST_DIR"
# Copy bundled resources
if [ -d "$RESOURCES_SRC/bundled" ]; then
echo "Copying bundled resources to $DEST_DIR/bundled"
rm -rf "$DEST_DIR/bundled"
cp -R "$RESOURCES_SRC/bundled" "$DEST_DIR/bundled"
else
echo "Warning: No bundled directory found at $RESOURCES_SRC/bundled" >&2
fi
if [ -n "$GIT_RELEASE_TAG" ]; then
VERSION_METADATA_DIR="$DEST_DIR/bundled/metadata"
VERSION_METADATA_PATH="$VERSION_METADATA_DIR/version.json"
echo "Writing bundled Warp version metadata to $VERSION_METADATA_PATH"
mkdir -p "$VERSION_METADATA_DIR"
printf '{\n "warp_version": "%s"\n}\n' "$GIT_RELEASE_TAG" > "$VERSION_METADATA_PATH"
fi
# Copy channel-gated skills matching the current release channel.
GATED_SRC="$REPO_ROOT/resources/channel-gated-skills"
DEST_SKILLS="$DEST_DIR/bundled/skills"
if [ -n "$CHANNEL" ] && [ -d "$GATED_SRC" ]; then
echo "Copying channel-gated skills for channel '$CHANNEL'..."
"$SCRIPT_DIR/copy_conditional_skills" \
"$CHANNEL" \
"$GATED_SRC" \
"$DEST_SKILLS"
fi
# Generate third-party license attribution.
#
# Additional (non-Cargo) third-party license files to include in the output.
# Each entry is: "Component Name|License Identifier|path/to/LICENSE/relative/to/repo/root"
# When adding a new third-party component to the bundle, add its license file
# to the repo alongside the component and add an entry here.
ADDITIONAL_LICENSES=(
"Hack Font|MIT|app/assets/bundled/fonts/hack/LICENSE.md"
"Roboto Font|SIL Open Font License|app/assets/bundled/fonts/roboto/LICENSE.txt"
"bash-preexec|MIT|app/assets/bundled/bootstrap/bash-preexec-LICENSE.md"
"Claude API Skill|Apache-2.0|resources/bundled/skills/claude-api/LICENSE.txt"
"rudder-sdk-rust|MIT|app/src/server/telemetry/LICENSE-RUDDER-SDK-RUST.txt"
"Windows Terminal|MIT|app/assets/windows/LICENSE-WINDOWS-TERMINAL"
"GitHub Desktop|MIT|app/src/code_review/GITHUB-DESKTOP-LICENSE"
)
# Build the third-party licenses file unless the NO_LICENSES envvar is set.
if [ -z "$NO_LICENSES" ]; then
LICENSES_OUTPUT="$DEST_DIR/THIRD_PARTY_LICENSES.txt"
echo "Generating third-party licenses at $LICENSES_OUTPUT"
cargo about generate \
--workspace \
--manifest-path "$REPO_ROOT/Cargo.toml" \
-c "$REPO_ROOT/about.toml" \
-o "$LICENSES_OUTPUT" \
"$REPO_ROOT/about.hbs"
# Append additional (non-Cargo) third-party licenses.
for entry in "${ADDITIONAL_LICENSES[@]}"; do
IFS='|' read -r name license_id license_path <<< "$entry"
license_file="$REPO_ROOT/$license_path"
if [ ! -f "$license_file" ]; then
echo "Error: License file not found: $license_file" >&2
exit 1
fi
printf '\n%s (%s)\n' "$name" "$license_id" >> "$LICENSES_OUTPUT"
printf '%0.s-' {1..80} >> "$LICENSES_OUTPUT"
printf '\n' >> "$LICENSES_OUTPUT"
cat "$license_file" >> "$LICENSES_OUTPUT"
printf '\n' >> "$LICENSES_OUTPUT"
done
fi
# Generate settings JSON schema unless explicitly skipped.
if [ "${SKIP_SETTINGS_SCHEMA:-}" != "1" ]; then
SCHEMA_OUTPUT="$DEST_DIR/settings_schema.json"
echo "Generating settings schema at $SCHEMA_OUTPUT"
SCHEMA_CMD=(cargo run)
if [ -n "$CARGO_PROFILE" ]; then
SCHEMA_CMD+=(--profile "$CARGO_PROFILE")
fi
SCHEMA_CMD+=(--manifest-path "$REPO_ROOT/Cargo.toml" --bin generate_settings_schema --)
if [ -n "$CHANNEL" ]; then
SCHEMA_CMD+=(--channel "$CHANNEL")
fi
SCHEMA_CMD+=("$SCHEMA_OUTPUT")
"${SCHEMA_CMD[@]}"
fi
echo "Successfully prepared bundled resources in $DEST_DIR"
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# Script to run presubmit checks. Devs can run this script locally before sending out PRs for review to ensure
# CI is passing for their PR.
set -e
cd "$(dirname "$0")/.."
FMT_COMMAND="cargo fmt"
echo "Running $FMT_COMMAND..."
set -e
EXIT_CODE=0
$FMT_COMMAND -- --check || EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
echo 'Run `'"$FMT_COMMAND"'` to fix.'
exit $EXIT_CODE
fi
echo "Cargo fmt succeeded..."
echo "Running clippy..."
# Exclude warp_completer because we run clippy on it with default features (rather than all features) below.
cargo clippy --workspace --exclude warp_completer --all-targets --all-features --tests -- -D warnings
# Run clippy on warp_completer with default, rather than all features enabled, because there is
# feature-gated logic for the WIP completions-on-js implementation.
cargo clippy -p warp_completer --all-targets --tests -- -D warnings
echo "clippy succeeded..."
echo "Running clang-format..."
./script/run-clang-format.py -r --extensions 'c,h,cpp,m' ./crates/warpui/src/ ./app/src/
echo "clang-format succeeded..."
echo "Running wgslfmt..."
# Normally a recursive glob pattern will do the trick, e.g. **/*.wgsl
# However, the default bash version in MacOS is too old to support that, so we use `find` instead.
find . -name "*.wgsl" -exec wgslfmt --check {} +
echo "wgslfmt succeeded..."
# check to see if we can run powershell on the device
if command -v pwsh /dev/null 2>&1; then
echo "Running PSScriptAnalyzer..."
./script/lint_powershell -ci
echo "PsScriptAnalyzer succeeded..."
elif [ "${GITHUB_ACTIONS}" == "true" ]; then
# If we are in CI, and fail to find powershell, we should automatically fail
echo "No powershell installation detected! Aborting!"
exit 1
else
# Otherwise, post a notice that we are skipping powershell
echo "No Powershell detected! skipping PSScriptAnalyzer!"
fi
echo "Running tests via nextest..."
cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
# Run warp_completer tests with the "v2" (completions-on-js) flag enabled. We do this to ensure that
# the v2 completions implementation doesn't regress/rot while it's development is paused.
cargo nextest run -p warp_completer --features v2
echo "Running doc tests..."
cargo test --doc
echo "Tests succeeded..."
echo "Congrats! All presubmits checks passed."
Executable
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
#
# Cross-platform entrypoint for running a local Warp build.
#
# On macOS this delegates to `./script/macos/run`, which builds and runs Warp
# as a real `.app` bundle (with code signing, plist updates, etc.). On Linux
# and Windows it invokes `cargo run` directly for the appropriate binary.
#
# This script owns all cross-platform setup (running install_channel_config,
# detecting internal vs OSS builds, and mapping legacy `--features` entries
# to environment variables), so platform-specific scripts can assume those
# values are already provided via environment variables.
set -e
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)"
cd "${REPO_ROOT}"
OS_TYPE="$(uname -s)"
FEATURES="gui"
./script/install_channel_config
# If warp_channel_config is on PATH, build the Local channel binary; otherwise build the OSS channel.
if command -v warp-channel-config &>/dev/null; then
WARP_BIN_NAME="warp"
WARP_CHANNEL="local"
else
WARP_BIN_NAME="warp-oss"
WARP_CHANNEL="oss"
fi
# Shared argument parsing. macOS-specific flags (--dont-open,
# --open_with_launchd, --generate-schema) are forwarded through MAC_ARGS and
# silently ignored on other platforms.
MAC_ARGS=()
CARGO_PARAMS=()
WARP_ARGS=()
while (( "$#" )); do
case "$1" in
--)
shift
WARP_ARGS=("$@")
break
;;
--features)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
FEATURES="$FEATURES,$2"
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--host-id)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
export WARP_CLOUD_MODE_DEFAULT_HOST="$2"
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--release)
CARGO_PARAMS+=("$1")
MAC_ARGS+=("$1")
shift
;;
--profile)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
CARGO_PARAMS+=("$1" "$2")
MAC_ARGS+=("$1" "$2")
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
*)
# Unknown flags (including macOS-only flags like --dont-open) and any
# positional arguments are forwarded to the platform-specific script.
MAC_ARGS+=("$1")
shift
;;
esac
done
# These cargo features were removed and replaced by environment variables read
# by warp-channel-config. Intercept them here so that existing --features
# invocations keep working.
for mapping in \
"with_local_server:WITH_LOCAL_SERVER" \
"with_local_session_sharing_server:WITH_LOCAL_SESSION_SHARING_SERVER" \
"with_sandbox_telemetry:WITH_SANDBOX_TELEMETRY"; do
feature="${mapping%%:*}"
env_var="${mapping##*:}"
if [[ ",$FEATURES," =~ ,"$feature", ]]; then
export "$env_var"=1
FEATURES="$(echo "$FEATURES" | sed "s/,$feature,/,/; s/^$feature,//; s/,$feature$//; s/^$feature$//")"
FEATURES="$(echo "$FEATURES" | sed 's/,,*/,/g; s/^,//; s/,$//')"
echo "Note: '$feature' is no longer a cargo feature; setting $env_var=1 instead."
fi
done
export FEATURES
export WARP_BIN_NAME
export WARP_CHANNEL
if [[ "$OS_TYPE" = "Darwin" ]]; then
if [[ ${#WARP_ARGS[@]} -gt 0 ]]; then
exec ./script/macos/run "${MAC_ARGS[@]}" -- "${WARP_ARGS[@]}"
else
exec ./script/macos/run "${MAC_ARGS[@]}"
fi
elif [[ "$OS_TYPE" = "Linux" ]] || [[ "$OS_TYPE" =~ ^(MINGW64_NT|MSYS_NT) ]]; then
echo "Running cargo run --bin $WARP_BIN_NAME --features \"$FEATURES\" ${CARGO_PARAMS[*]}"
if [[ ${#WARP_ARGS[@]} -gt 0 ]]; then
cargo run --bin "$WARP_BIN_NAME" --features "$FEATURES" "${CARGO_PARAMS[@]}" -- "${WARP_ARGS[@]}"
else
cargo run --bin "$WARP_BIN_NAME" --features "$FEATURES" "${CARGO_PARAMS[@]}"
fi
else
echo "No run script defined for the current platform ($OS_TYPE)!" >&2
exit 1
fi
+408
View File
@@ -0,0 +1,408 @@
#!/usr/bin/env python3
"""A wrapper script around clang-format, suitable for linting multiple files
and to use for continuous integration.
This is an alternative API for the clang-format command line.
It runs over multiple files and directories in parallel.
A diff output is produced and a sensible exit code is returned.
"""
from __future__ import print_function, unicode_literals
import argparse
import codecs
import difflib
import fnmatch
import io
import errno
import multiprocessing
import os
import signal
import subprocess
import sys
import traceback
from functools import partial
try:
from subprocess import DEVNULL # py3k
except ImportError:
DEVNULL = open(os.devnull, "wb")
DEFAULT_EXTENSIONS = 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx'
DEFAULT_CLANG_FORMAT_IGNORE = '.clang-format-ignore'
class ExitStatus:
SUCCESS = 0
DIFF = 1
TROUBLE = 2
def excludes_from_file(ignore_file):
excludes = []
try:
with io.open(ignore_file, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('#'):
# ignore comments
continue
pattern = line.rstrip()
if not pattern:
# allow empty lines
continue
excludes.append(pattern)
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
return excludes;
def list_files(files, recursive=False, extensions=None, exclude=None):
if extensions is None:
extensions = []
if exclude is None:
exclude = []
out = []
for file in files:
if recursive and os.path.isdir(file):
for dirpath, dnames, fnames in os.walk(file):
fpaths = [os.path.join(dirpath, fname) for fname in fnames]
for pattern in exclude:
# os.walk() supports trimming down the dnames list
# by modifying it in-place,
# to avoid unnecessary directory listings.
dnames[:] = [
x for x in dnames
if
not fnmatch.fnmatch(os.path.join(dirpath, x), pattern)
]
fpaths = [
x for x in fpaths if not fnmatch.fnmatch(x, pattern)
]
for f in fpaths:
ext = os.path.splitext(f)[1][1:]
if ext in extensions:
out.append(f)
else:
out.append(file)
return out
def make_diff(file, original, reformatted):
return list(
difflib.unified_diff(
original,
reformatted,
fromfile='{}\t(original)'.format(file),
tofile='{}\t(reformatted)'.format(file),
n=3))
class DiffError(Exception):
def __init__(self, message, errs=None):
super(DiffError, self).__init__(message)
self.errs = errs or []
class UnexpectedError(Exception):
def __init__(self, message, exc=None):
super(UnexpectedError, self).__init__(message)
self.formatted_traceback = traceback.format_exc()
self.exc = exc
def run_clang_format_diff_wrapper(args, file):
try:
ret = run_clang_format_diff(args, file)
return ret
except DiffError:
raise
except Exception as e:
raise UnexpectedError('{}: {}: {}'.format(file, e.__class__.__name__,
e), e)
def run_clang_format_diff(args, file):
try:
with io.open(file, 'r', encoding='utf-8') as f:
original = f.readlines()
except IOError as exc:
raise DiffError(str(exc))
if args.in_place:
invocation = [args.clang_format_executable, '-i', file]
else:
invocation = [args.clang_format_executable, file]
if args.style:
invocation.extend(['--style', args.style])
if args.dry_run:
print(" ".join(invocation))
return [], []
# Use of utf-8 to decode the process output.
#
# Hopefully, this is the correct thing to do.
#
# It's done due to the following assumptions (which may be incorrect):
# - clang-format will returns the bytes read from the files as-is,
# without conversion, and it is already assumed that the files use utf-8.
# - if the diagnostics were internationalized, they would use utf-8:
# > Adding Translations to Clang
# >
# > Not possible yet!
# > Diagnostic strings should be written in UTF-8,
# > the client can translate to the relevant code page if needed.
# > Each translation completely replaces the format string
# > for the diagnostic.
# > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation
#
# It's not pretty, due to Python 2 & 3 compatibility.
encoding_py3 = {}
if sys.version_info[0] >= 3:
encoding_py3['encoding'] = 'utf-8'
try:
proc = subprocess.Popen(
invocation,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
**encoding_py3)
except OSError as exc:
raise DiffError(
"Command '{}' failed to start: {}".format(
subprocess.list2cmdline(invocation), exc
)
)
proc_stdout = proc.stdout
proc_stderr = proc.stderr
if sys.version_info[0] < 3:
# make the pipes compatible with Python 3,
# reading lines should output unicode
encoding = 'utf-8'
proc_stdout = codecs.getreader(encoding)(proc_stdout)
proc_stderr = codecs.getreader(encoding)(proc_stderr)
# hopefully the stderr pipe won't get full and block the process
outs = list(proc_stdout.readlines())
errs = list(proc_stderr.readlines())
proc.wait()
if proc.returncode:
raise DiffError(
"Command '{}' returned non-zero exit status {}".format(
subprocess.list2cmdline(invocation), proc.returncode
),
errs,
)
if args.in_place:
return [], errs
return make_diff(file, original, outs), errs
def bold_red(s):
return '\x1b[1m\x1b[31m' + s + '\x1b[0m'
def colorize(diff_lines):
def bold(s):
return '\x1b[1m' + s + '\x1b[0m'
def cyan(s):
return '\x1b[36m' + s + '\x1b[0m'
def green(s):
return '\x1b[32m' + s + '\x1b[0m'
def red(s):
return '\x1b[31m' + s + '\x1b[0m'
for line in diff_lines:
if line[:4] in ['--- ', '+++ ']:
yield bold(line)
elif line.startswith('@@ '):
yield cyan(line)
elif line.startswith('+'):
yield green(line)
elif line.startswith('-'):
yield red(line)
else:
yield line
def print_diff(diff_lines, use_color):
if use_color:
diff_lines = colorize(diff_lines)
if sys.version_info[0] < 3:
sys.stdout.writelines((l.encode('utf-8') for l in diff_lines))
else:
sys.stdout.writelines(diff_lines)
def print_trouble(prog, message, use_colors):
error_text = 'error:'
if use_colors:
error_text = bold_red(error_text)
print("{}: {} {}".format(prog, error_text, message), file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'--clang-format-executable',
metavar='EXECUTABLE',
help='path to the clang-format executable',
default='clang-format')
parser.add_argument(
'--extensions',
help='comma separated list of file extensions (default: {})'.format(
DEFAULT_EXTENSIONS),
default=DEFAULT_EXTENSIONS)
parser.add_argument(
'-r',
'--recursive',
action='store_true',
help='run recursively over directories')
parser.add_argument(
'-d',
'--dry-run',
action='store_true',
help='just print the list of files')
parser.add_argument(
'-i',
'--in-place',
action='store_true',
help='format file instead of printing differences')
parser.add_argument('files', metavar='file', nargs='+')
parser.add_argument(
'-q',
'--quiet',
action='store_true',
help="disable output, useful for the exit code")
parser.add_argument(
'-j',
metavar='N',
type=int,
default=0,
help='run N clang-format jobs in parallel'
' (default number of cpus + 1)')
parser.add_argument(
'--color',
default='auto',
choices=['auto', 'always', 'never'],
help='show colored diff (default: auto)')
parser.add_argument(
'-e',
'--exclude',
metavar='PATTERN',
action='append',
default=[],
help='exclude paths matching the given glob-like pattern(s)'
' from recursive search')
parser.add_argument(
'--style',
help='formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)')
args = parser.parse_args()
# use default signal handling, like diff return SIGINT value on ^C
# https://bugs.python.org/issue14229#msg156446
signal.signal(signal.SIGINT, signal.SIG_DFL)
try:
signal.SIGPIPE
except AttributeError:
# compatibility, SIGPIPE does not exist on Windows
pass
else:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
colored_stdout = False
colored_stderr = False
if args.color == 'always':
colored_stdout = True
colored_stderr = True
elif args.color == 'auto':
colored_stdout = sys.stdout.isatty()
colored_stderr = sys.stderr.isatty()
version_invocation = [args.clang_format_executable, str("--version")]
try:
subprocess.check_call(version_invocation, stdout=DEVNULL)
except subprocess.CalledProcessError as e:
print_trouble(parser.prog, str(e), use_colors=colored_stderr)
return ExitStatus.TROUBLE
except OSError as e:
print_trouble(
parser.prog,
"Command '{}' failed to start: {}".format(
subprocess.list2cmdline(version_invocation), e
),
use_colors=colored_stderr,
)
return ExitStatus.TROUBLE
retcode = ExitStatus.SUCCESS
excludes = excludes_from_file(DEFAULT_CLANG_FORMAT_IGNORE)
excludes.extend(args.exclude)
files = list_files(
args.files,
recursive=args.recursive,
exclude=excludes,
extensions=args.extensions.split(','))
if not files:
return
njobs = args.j
if njobs == 0:
njobs = multiprocessing.cpu_count() + 1
njobs = min(len(files), njobs)
if njobs == 1:
# execute directly instead of in a pool,
# less overhead, simpler stacktraces
it = (run_clang_format_diff_wrapper(args, file) for file in files)
pool = None
else:
pool = multiprocessing.Pool(njobs)
it = pool.imap_unordered(
partial(run_clang_format_diff_wrapper, args), files)
pool.close()
while True:
try:
outs, errs = next(it)
except StopIteration:
break
except DiffError as e:
print_trouble(parser.prog, str(e), use_colors=colored_stderr)
retcode = ExitStatus.TROUBLE
sys.stderr.writelines(e.errs)
except UnexpectedError as e:
print_trouble(parser.prog, str(e), use_colors=colored_stderr)
sys.stderr.write(e.formatted_traceback)
retcode = ExitStatus.TROUBLE
# stop at the first unexpected error,
# something could be very wrong,
# don't process all files unnecessarily
if pool:
pool.terminate()
break
else:
sys.stderr.writelines(errs)
if outs == []:
continue
if not args.quiet:
print_diff(outs, use_color=colored_stdout)
if retcode == ExitStatus.SUCCESS:
retcode = ExitStatus.DIFF
if pool:
pool.join()
return retcode
if __name__ == '__main__':
sys.exit(main())
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# These env vars must be set:
# SENTRY_PROJECT
# SENTRY_AUTH_TOKEN
# SENTRY_ORG
# SENTRY_ENVIRONMENT
# RELEASE_VERSION
# This script is adapted from getsentry/action-release, a JS GitHub Action. See
# the relevant source code here: https://github.com/getsentry/action-release/blob/master/src/main.ts.
if which sentry-cli >/dev/null; then
# Create the new release.
ERROR=$(sentry-cli releases new "$RELEASE_VERSION")
if [ $? -ne 0 ]; then
echo "::error title=Error creating Sentry release::$ERROR"
fi
# Set the commits for the release automatically.
ERROR=$(sentry-cli releases set-commits --auto "$RELEASE_VERSION")
if [ $? -ne 0 ]; then
echo "::error title=Error setting commits for Sentry release::$ERROR"
fi
# Add a deploy for the release.
ERROR=$(sentry-cli deploys new --release "$RELEASE_VERSION" --env "$SENTRY_ENVIRONMENT")
if [ $? -ne 0 ]; then
echo "::error title=Error adding deploy for Sentry release::$ERROR"
fi
# Finalize the release.
ERROR=$(sentry-cli releases finalize "$RELEASE_VERSION")
if [ $? -ne 0 ]; then
echo "::error title=Error finalizing Sentry release::$ERROR"
fi
else
NOT_INSTALLED="sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases"
echo "::error title=Error creating Sentry release::$NOT_INSTALLED"
fi
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# These env vars must be set:
# SENTRY_PROJECT
# SENTRY_AUTH_TOKEN
# SENTRY_ORG
# DEBUG_FILE_OR_FOLDER_PATH
set -x
if which sentry-cli >/dev/null; then
ERROR="$(sentry-cli upload-dif "$DEBUG_FILE_OR_FOLDER_PATH")"
if [ ! $? -eq 0 ]; then
echo "warning: sentry-cli - $ERROR"
fi
else
echo "warning: sentry-cli not installed, download from https://github.com/getsentry/sentry-cli/releases"
fi
+266
View File
@@ -0,0 +1,266 @@
#!/bin/bash
#
# Updates the Info.plist file for a macOS app bundle to register supported file types and URL schemes.
# This only applies the updates common to `script/bundle` and `script/run`.
# Must be called from the root directory of the warp repo.
set -e
if [[ ! -d "script" ]]; then
echo "Run this script from the root of your warp repo."
exit 1
fi
if [[ -z "$WARP_PLIST_PATH" ]]; then
echo 'Must set $WARP_PLIST_PATH'
exit 1
fi
if [[ -n "$GIT_RELEASE_TAG" ]]; then
echo "Updating plist Warp version to $GIT_RELEASE_TAG"
plutil -insert WarpVersion -string "$GIT_RELEASE_TAG" "$WARP_PLIST_PATH"
# We convert our version format to be period-separated integers only to align better with
# Apple's guidance on values for `CFBundleVersion` / `CFBundleShortVersionString`.
# (They technically only allow [major].[minor].[patch], but it's not enforced.)
# This allows IT admins to work with a more stable version of our internal versioning scheme.
# A tag like `v0.2025.01.07.08.02.stable_02` would be converted to `0.2025.01.07.08.02.02`.
if [[ $GIT_RELEASE_TAG =~ ^v([0-9]+)\.([0-9]{4})\.([0-9]{2})\.([0-9]{2})\.([0-9]{2})\.([0-9]{2})\.[a-z]+_([0-9]{2})$ ]]; then
MAJOR="${BASH_REMATCH[1]}"
YEAR="${BASH_REMATCH[2]}"
MONTH="${BASH_REMATCH[3]}"
DAY="${BASH_REMATCH[4]}"
HOUR="${BASH_REMATCH[5]}"
MINUTE="${BASH_REMATCH[6]}"
BUILD="${BASH_REMATCH[7]}"
FORMATTED_VERSION="${MAJOR}.${YEAR}.${MONTH}.${DAY}.${HOUR}.${MINUTE}.${BUILD}"
echo "Updating plist version to $FORMATTED_VERSION"
plutil -replace CFBundleShortVersionString -string "$FORMATTED_VERSION" "$WARP_PLIST_PATH"
plutil -replace CFBundleVersion -string "$FORMATTED_VERSION" "$WARP_PLIST_PATH"
else
echo "Warning: GIT_RELEASE_TAG '$GIT_RELEASE_TAG' does not match expected format vN.YYYY.MM.DD.HH.MM.channel_NN"
fi
fi
if [[ -n "$WARP_SCHEME_NAME" ]]; then
echo "Updating plist to support a custom url scheme"
# Update the plist that cargo bundle creates to add support for custom URL schemes. Unfortunately, cargo bundle does not support
# setting arbitrary plist fields, so we must do this after the fact.
plutil -insert CFBundleURLTypes -xml "<array><dict><key>CFBundleURLName</key><string>Custom App</string><key>CFBundleURLSchemes</key><array><string>$WARP_SCHEME_NAME</string></array></dict></array>" "$WARP_PLIST_PATH"
fi
if [[ -z "$WARP_PLIST_NO_FILE_TYPES" ]]; then
echo "Updating plist with supported file types"
plutil -insert CFBundleDocumentTypes -xml "
<array>
<dict>
<key>CFBundleTypeName</key><string>Folder</string>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.folder</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>Terminal shell script</string>
<key>CFBundleTypeRole</key><string>Shell</string>
<key>LSItemContentTypes</key>
<array>
<string>com.apple.terminal.shell-script</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>Unix Executable File</string>
<key>CFBundleTypeRole</key><string>Shell</string>
<key>LSItemContentTypes</key>
<array>
<string>public.unix-executable</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>Markdown File</string>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>net.daringfireball.markdown</string>
<string>com.unknown.md</string>
<string>net.ia.markdown</string>
<string>public.markdown</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>Plain Text File</string>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.plain-text</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>Source Code File</string>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.source-code</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>Source Code</string>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>LSHandlerRank</key><string>Alternate</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>c</string>
<string>h</string>
<string>cc</string>
<string>cpp</string>
<string>cxx</string>
<string>c++</string>
<string>hpp</string>
<string>hxx</string>
<string>hh</string>
<string>h++</string>
<string>m</string>
<string>mm</string>
<string>cs</string>
<string>csx</string>
<string>css</string>
<string>dart</string>
<string>dockerfile</string>
<string>containerfile</string>
<string>go</string>
<string>htm</string>
<string>html</string>
<string>xhtml</string>
<string>java</string>
<string>jav</string>
<string>js</string>
<string>mjs</string>
<string>cjs</string>
<string>json</string>
<string>jsx</string>
<string>less</string>
<string>lua</string>
<string>makefile</string>
<string>mk</string>
<string>cmake</string>
<string>php</string>
<string>pl</string>
<string>pm</string>
<string>py</string>
<string>pyi</string>
<string>r</string>
<string>rb</string>
<string>gemspec</string>
<string>rs</string>
<string>rst</string>
<string>sass</string>
<string>scss</string>
<string>sql</string>
<string>swift</string>
<string>ts</string>
<string>tsx</string>
<string>txt</string>
<string>vue</string>
<string>xml</string>
<string>xaml</string>
<string>dtd</string>
<string>plist</string>
<string>yaml</string>
<string>yml</string>
<string>eyaml</string>
<string>eyml</string>
<string>toml</string>
<string>cfg</string>
<string>conf</string>
<string>config</string>
<string>csv</string>
<string>diff</string>
<string>env</string>
<string>gradle</string>
<string>groovy</string>
<string>ini</string>
<string>log</string>
<string>properties</string>
<string>svg</string>
<string>tex</string>
<string>cls</string>
<string>lock</string>
<string>bat</string>
<string>cmd</string>
<string>ps1</string>
<string>psd1</string>
<string>psm1</string>
<string>sh</string>
<string>bash</string>
<string>zsh</string>
<string>fish</string>
<string>bashrc</string>
<string>bash_profile</string>
<string>zshrc</string>
<string>zshenv</string>
<string>profile</string>
<string>gitignore</string>
<string>gitconfig</string>
<string>gitattributes</string>
<string>editorconfig</string>
<string>hbs</string>
<string>handlebars</string>
<string>erb</string>
<string>tf</string>
<string>tfvars</string>
<string>proto</string>
<string>graphql</string>
<string>gql</string>
<string>wgsl</string>
<string>zig</string>
<string>kt</string>
<string>kts</string>
<string>scala</string>
<string>ex</string>
<string>exs</string>
<string>erl</string>
<string>clj</string>
<string>cljs</string>
<string>coffee</string>
<string>fs</string>
<string>fsi</string>
<string>fsx</string>
<string>ml</string>
<string>mli</string>
<string>vb</string>
<string>pug</string>
<string>jade</string>
<string>ipynb</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key><string>All Documents</string>
<key>CFBundleTypeRole</key><string>Editor</string>
<key>LSHandlerRank</key><string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
</dict>
</array>" "$WARP_PLIST_PATH"
fi
# Unfortunately, there isn't a single standard UTI (what goes in LSItemContentTypes) for Markdown
# files. Instead, we list all the common ones - see these for more info:
# https://github.com/sbarex/QLMarkdown#installation
# https://blog.smittytone.net/2021/01/19/how-tools-try-to-own-markdown/
echo "Updating plist with permissions descriptions"
plutil -insert NSAppleEventsUsageDescription -string "A program in Warp wants to use AppleScript." "$WARP_PLIST_PATH"
plutil -insert NSCameraUsageDescription -string "A program in Warp wants to use the camera." "$WARP_PLIST_PATH"
plutil -insert NSMicrophoneUsageDescription -string "A program in Warp wants to use your microphone." "$WARP_PLIST_PATH"
plutil -insert NSContactsUsageDescription -string "A program in Warp wants to use your contacts." "$WARP_PLIST_PATH"
plutil -insert NSCalendarsUsageDescription -string "A program in Warp wants to use your calendar." "$WARP_PLIST_PATH"
plutil -insert NSLocationUsageDescription -string "A program in Warp wants to use your location information." "$WARP_PLIST_PATH"
plutil -insert NSPhotoLibraryUsageDescription -string "A program in Warp wants to use your photo library." "$WARP_PLIST_PATH"
echo "Disabling use of Liquid Glass on macOS Tahoe"
plutil -insert UIDesignRequiresCompatibility -bool true "$WARP_PLIST_PATH"
+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.2598 3.22483C12.2526 3.23454 12.2495 3.24758 12.2432 3.27366L8.70963 17.879C8.6999 17.9192 8.69504 17.9393 8.70002 17.9551C8.70438 17.969 8.71366 17.9808 8.72611 17.9883C8.74031 17.9969 8.761 17.9969 8.80236 17.9969H20.5658C21.9102 17.9969 23 16.8707 23 15.4815V5.71604C23 4.32685 21.9102 3.20068 20.5658 3.20068H12.3359C12.3091 3.20068 12.2957 3.20068 12.2845 3.20539C12.2747 3.20954 12.2662 3.21626 12.2598 3.22483Z" fill="#FF0000"/>
<path d="M10.0849 6.04543C10.0899 6.06122 10.085 6.08129 10.0754 6.12145L6.91285 19.3058C6.90322 19.3459 6.8984 19.366 6.9034 19.3818C6.90778 19.3956 6.91706 19.4074 6.9295 19.4149C6.94369 19.4234 6.96433 19.4234 7.00563 19.4234H11.8568C11.8981 19.4234 11.9187 19.4234 11.9329 19.432C11.9454 19.4395 11.9547 19.4512 11.959 19.4651C11.964 19.4809 11.9592 19.5009 11.9496 19.5411L11.6652 20.7268C11.6589 20.7529 11.6558 20.766 11.6486 20.7758C11.6422 20.7843 11.6337 20.7911 11.6239 20.7952C11.6127 20.8 11.5993 20.8 11.5724 20.8H3.41346C2.08054 20.8 1 19.6738 1 18.2846V8.51913C1 7.12995 2.08054 6.00378 3.41346 6.00378H9.98264C10.0239 6.00378 10.0446 6.00378 10.0588 6.01233C10.0712 6.01981 10.0805 6.03158 10.0849 6.04543Z" fill="#FF0000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env bash
#
# Builds a Warp binary and bundles it up for distribution.
set -e
# On macOS, we need to use LLVM's clang for wasm32-unknown-unknown target
if [[ "$OSTYPE" == "darwin"* ]]; then
LLVM_CLANG="/opt/homebrew/opt/llvm/bin/clang"
if [ ! -f "$LLVM_CLANG" ]; then
echo "Error: LLVM clang not found at $LLVM_CLANG"
echo "Please install it with: brew install llvm"
exit 1
fi
export CC_wasm32_unknown_unknown="$LLVM_CLANG"
fi
WORKSPACE_ROOT_DIR="$(pwd)"
CARGO_TARGET_DIR="$WORKSPACE_ROOT_DIR/target/wasm32-unknown-unknown"
# By default we build dev bundles.
RELEASE_CHANNEL="dev"
# TODO: We should enable crash_reporting and before enabling for trusted testers.
# https://linear.app/warpdotdev/issue/PLAT-428/crash-reporting-on-web
FEATURES="release_bundle,gui"
DEBUG=false
PARAMS=""
while (( "$#" )); do
case "$1" in
--debug)
DEBUG=true
shift
;;
--check-only)
echo 'Only running `cargo check` and not producing a bundle.'
CHECK_ONLY="true"
shift
;;
--no-split)
NO_SPLIT="true"
shift
;;
--nouniversal)
# Discard the --nouniversal argument, which is only used for macOS
# bundles.
shift
;;
-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
;;
--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
;;
--features)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
echo "Setting features to $2"
FEATURES_OVERRIDE=$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"
if [[ $DEBUG = true ]]; then
CARGO_PROFILE="dev-wasm"
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-wasm-debug_assertions"
else
CARGO_PROFILE="release-wasm"
fi
# The `dev` profile needs to be special-cased for historical
# reasons: https://doc.rust-lang.org/cargo/guide/build-cache.html#build-cache.
# We don't use the `dev` profile today when bundling for wasm
# but this is left in the script to future-proof the use of the `dev` profile.
if [[ "$CARGO_PROFILE" == "dev" ]]; then
CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_DIR/debug"
else
CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_DIR/$CARGO_PROFILE"
fi
OUT_DIR="$CARGO_TARGET_OUTPUT_DIR/bundle/wasm"
mkdir -p "$OUT_DIR"
ASSET_TARGET_DIR="$CARGO_TARGET_OUTPUT_DIR/bundle/assets"
mkdir -p "$ASSET_TARGET_DIR"
EXTRAS_DIR="$CARGO_TARGET_OUTPUT_DIR/bundle/extras"
mkdir -p "$EXTRAS_DIR"
# Update parameters based on the target release channel.
#
# WARP_BIN is the name of the binary produced by cargo.
# N.B. The bundled outputs will always be warp.js and warp_bg.wasm.
if [[ $RELEASE_CHANNEL = "local" ]]; then
WARP_BIN="warp"
FEATURES="$FEATURES,remote_tty"
elif [[ $RELEASE_CHANNEL = "dev" ]]; then
WARP_BIN="dev"
elif [[ $RELEASE_CHANNEL = "preview" ]]; then
WARP_BIN="preview"
FEATURES="$FEATURES,preview_channel"
elif [[ $RELEASE_CHANNEL = "stable" ]]; then
WARP_BIN="stable"
fi
if [ -n "${FEATURES_OVERRIDE+x}" ]; then
FEATURES="$FEATURES_OVERRIDE"
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
ASSET_TARGET_DIR="$ASSET_TARGET_DIR" cargo check --target wasm32-unknown-unknown --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES"
exit 0
fi
# Build the wasm binary.
echo "Building and bundling Warp for channel $RELEASE_CHANNEL"
ASSET_TARGET_DIR="$ASSET_TARGET_DIR" cargo build --target wasm32-unknown-unknown --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES"
# Generate linked JS and wasm files that can be run in the browser.
echo "Running wasm-bindgen on $CARGO_TARGET_OUTPUT_DIR/$WARP_BIN.wasm"
wasm-bindgen --target web --out-dir "$OUT_DIR" --out-name "warp" --keep-debug --no-typescript "$CARGO_TARGET_OUTPUT_DIR/$WARP_BIN.wasm"
WASM_BINARY_OUT="${OUT_DIR}/warp_bg.wasm"
WASM_BINARY_DEBUG="${EXTRAS_DIR}/warp_bg.debug.wasm"
if [[ "$NO_SPLIT" != "true" ]]; then
# Run Sentry's wasm-split binary to separate out debug information from the
# wasm binary.
#
# Binary releases of wasm-split can be found here:
# https://github.com/getsentry/symbolicator/releases
wasm-split "$WASM_BINARY_OUT" --debug-out "$WASM_BINARY_DEBUG" --strip --strip-names
fi
# If this is being run within a GitHub action, set an output variable with the
# directory containing all built packages.
if [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "::echo::on"
echo "packages_dir=$OUT_DIR" >> "$GITHUB_OUTPUT"
echo "assets_dir=$ASSET_TARGET_DIR" >> "$GITHUB_OUTPUT"
echo "debug_executable_path=$WASM_BINARY_DEBUG" >> "$GITHUB_OUTPUT"
echo "::echo::off"
fi
+33
View File
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>warp</title>
<style type="text/css">
body {
margin: 0px;
background-color: black;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
height: 100svh;
outline: none;
}
</style>
</head>
<body>
<script type="module">
import init from "/assets/client/wasm/warp.js";
window.addEventListener("load", () => {
init();
});
</script>
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# Install all dependencies required to build Warp on Web.
set -e
# Install Rust and other development libraries needed in the compilation
# process. Web and Linux are both built on Linux runners, so we can reuse
# the linux build dependencies script here.
"$PWD"/script/linux/install_build_deps
# Make sure the wasm Rust toolchain target is available.
rustup target add wasm32-unknown-unknown
# Install cargo-managed dependencies required to build for wasm.
# The wasm-bindgen version installed here must be kept in sync with the version
# used in the project.
WASM_BINDGEN_VERSION="$(cargo metadata --format-version 1 | jq -rc '.packages[] | select(.name == "wasm-bindgen") | .version')"
cargo binstall --force -y wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION"
cargo binstall --force -y wasm-opt
# Install wasm-split binary.
mkdir -p "$HOME/.local/bin"
cd "$HOME/.local/bin"
curl -L https://github.com/getsentry/symbolicator/releases/download/26.3.1/wasm-split-Linux-x86_64 -o wasm-split
chmod +x wasm-split
# Export .local/bin into the path so we can find the wasm-split binary.
echo "$HOME/.local/bin" >> $GITHUB_PATH
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -eo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)"
cd "${REPO_ROOT}"
WASM_BINDGEN_VERSION="$(cargo metadata --format-version 1 | jq -rc '.packages[] | select(.name == "wasm-bindgen") | .version')"
cargo install wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION"
# We source the bundle script so we can access it's calculated $OUT_DIR.
source ./script/wasm/bundle --debug --no-split --channel local "$@"
BUNDLE_DIR="$(dirname "$OUT_DIR")"
cp "$WORKSPACE_ROOT_DIR/script/wasm/dev-index.html" "$BUNDLE_DIR/index.html"
echo "Built Warp to $BUNDLE_DIR"
cargo run --release --package serve-wasm -- "$BUNDLE_DIR"
+73
View File
@@ -0,0 +1,73 @@
# Inno Setup installer script
## What is `windows-installer.iss`?
On Windows, programs are conventionally installed using an installer, also known as an installation wizard.
The installer is a single executable that takes care of:
* Creating a directory to store the program's files
* Downloading assets
* Initializing registry entries
* Creating a desktop icon
* ... and more, depending on the application's needs.
`windows-installer.iss` is an **Inno Setup script**:
a configuration file for building a Warp installer.
The Inno Setup Compiler takes a script file and generates an installer executable.
This is roughly equivalent to the bundling process on MacOS.
## How to edit the installer
See the Inno Setup documentation: [Inno Setup Help](https://jrsoftware.org/ishelp/).
This script can be edited manually using any code editor.
However, it requires the Inno Setup compiler to be turned into a `.exe` file.
## How to compile this installer
First, ensure you've set up your environment.
* Download and install the [Inno Setup Compiler](https://jrsoftware.org/isdl.php).
* Run `cargo build` to ensure the installer uses the latest version of Warp.
### Option 1: Use the CLI
1. Add the Inno Setup Command-line Compiler executable to your shell path.
By default, it is located at `C:\Program Files (x86)\Inno Setup 6\ISCC.exe`.
2. Compile the installer:
```shell
iscc .\script\windows\windows-installer.iss`.
```
3. Run the generated executable:
```shell
.\script\windows\Output\Warp-Windows-Setup.exe`.
```
The script begins with a series of preprocessor definitions.
From the command line, use the `/D` flag to emulate preprocessor definitions
and override the hardcoded defaults.
Usage: `iscc <script path> /D<name>[=<value>]`
The following constants can be overwritten:
* `MyAppVersion` (default: `0.1.0`)
* `MyAppExeName` (default: `warp.exe`)
* `ReleaseChannel` (default: `dev`)
* `TargetProfileDir` (default: `debug`)
### Option 2: Use the GUI
1. Open the Inno Setup application and select this script.
2. Click the "compile" button. This will generate an installer executable in a directory called `Output` at the same level as this script.
2. To run the installer, click the "run" button in Inno Setup.
## Using icons
Windows has its own icon file format that bundles together multiple icon sizes.
App icons are located in `app/channels/<channel_name>/icon/no-padding`.
The `.ico` files are generated using imagemagick:
```shell
convert 16x16.png 32x32.png 48x48.png 64x64.png 256x256.png icon.ico
```
Note that sizes above 256x256 are not supported.
See the [Inno Setup docs](https://jrsoftware.org/ishelp/index.php?topic=setup_setupiconfile).
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env powershell
$ErrorActionPreference = 'Stop'
# Git for Windows can be installed system-wide (Program Files) or per-user (LOCALAPPDATA\Programs\Git).
$gitBinCandidates = @(
"$env:PROGRAMFILES\Git\bin",
"$env:LOCALAPPDATA\Programs\Git\bin"
)
$gitBinDir = $gitBinCandidates | Where-Object { Test-Path -PathType Container $_ } | Select-Object -First 1
if (-not $gitBinDir) {
Write-Error 'Git for Windows is required. Please install it at:'
Write-Error 'https://gitforwindows.org/'
exit 1
}
if (-not (Get-Command -Name cargo -Type Application -ErrorAction SilentlyContinue)) {
Write-Output 'Installing rust...'
Invoke-WebRequest -Uri 'https://win.rustup.rs/x86_64' -OutFile "$env:Temp\rustup-init.exe"
& "$env:Temp\rustup-init.exe"
Write-Output 'Please start a new terminal session so that cargo is in your PATH'
exit 1
}
# A bash executable should come with Git for Windows
& "$gitBinDir\bash.exe" "$PWD\script\install_cargo_test_deps"
# Needed in wasm compilation for parsing the version of wasm-bindgen
winget install jqlang.jq
# CMake is needed to build some dependencies, e.g.: sentry-contrib-native.
winget install -e --id Kitware.CMake
# We use InnoSetup to build our release bundle installer.
winget install -e --id JRSoftware.InnoSetup
# If we don't see gcloud command, try adding the install location to the PATH.
if (-not (Get-Command -Name gcloud -Type Application -ErrorAction SilentlyContinue)) {
$env:PATH += ";$env:LOCALAPPDATA\Google\Cloud SDK\google-cloud-sdk\bin"
}
# If we still don't see it, install it.
if (-not (Get-Command -Name gcloud -Type Application -ErrorAction SilentlyContinue)) {
(New-Object Net.WebClient).DownloadFile('https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe', "$env:Temp\GoogleCloudSDKInstaller.exe")
Start-Process "$env:Temp\GoogleCloudSDKInstaller.exe" -Wait
}
[string]$identityToken = gcloud auth print-identity-token
if ($identityToken.Trim().Length -eq 0) {
Write-Output 'gcloud CLI authentication missing. Press enter to continue...'
Read-Host
gcloud auth login
}
+215
View File
@@ -0,0 +1,215 @@
#!/usr/bin/env powershell
#
# Bundle the application for release.
Param (
# Build dev bundles by default.
[Switch]$DEBUG_BUILD = $False,
[Alias('check-only')]
[Switch]$CHECK_ONLY,
[ValidateSet('local', 'dev', 'preview', 'stable')]
[String]$CHANNEL = 'dev',
[Alias('release-tag')]
[String]$RELEASE_TAG = '',
[String]$FEATURES = 'release_bundle,crash_reporting,gui',
# Builds only the Warp binary, skips the installer.
[Switch]$SKIP_BUILD_INSTALLER = $False,
# Builds only the installer, skips the Warp binary. Use this if the Warp
# binary has already been built.
[Switch]$SKIP_BUILD_BINARY = $False,
[ValidateSet('x64', 'arm64')]
[String]$ARCH = '',
# A signtool command for Inno Setup to sign the setup engine and uninstaller.
# Uses $f as the file placeholder, e.g.:
# 'signtool.exe sign /fd SHA256 ... $f'
# When empty, the installer is built without signing.
[Alias('sign-tool-cmd')]
[String]$SIGN_TOOL_CMD = ''
)
if ($RELEASE_TAG) {
$env:GIT_RELEASE_TAG = $RELEASE_TAG
}
# Use provided ARCH parameter if set, otherwise detect from system
if (-not $ARCH) {
if ($env:PROCESSOR_ARCHITECTURE -eq 'AMD64') {
$ARCH = 'x64'
} elseif ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') {
$ARCH = 'arm64'
} else {
throw "Unsupported processor architecture: $env:PROCESSOR_ARCHITECTURE"
}
}
if ($ARCH -eq 'arm64') {
$FILE_ENDING = 'Setup-arm64'
$PLATFORM_TARGET = 'aarch64-pc-windows-msvc'
} else {
# If x64, then we just use the filename "WarpSetup.exe" for example
$FILE_ENDING = 'Setup'
$PLATFORM_TARGET = 'x86_64-pc-windows-msvc'
}
$ErrorActionPreference = 'Stop'
$WORKSPACE_ROOT_DIR = $(Get-Location).Path
$CARGO_TARGET_DIR = $WORKSPACE_ROOT_DIR + '\target'
$WINDOWS_INSTALLER_DIR = $WORKSPACE_ROOT_DIR + '\script\windows'
if ($DEBUG_BUILD) {
$CARGO_PROFILE = 'dev'
} elseif (("$CHANNEL" -eq 'local') -or ("$CHANNEL" -eq 'dev')) {
# 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 = 'rltoda'
} else {
$CARGO_PROFILE = 'rlto'
}
if ($CARGO_PROFILE -eq 'dev') {
$CARGO_TARGET_OUTPUT_DIR = "$CARGO_TARGET_DIR" + '\' + $PLATFORM_TARGET + '\debug'
} else {
$CARGO_TARGET_OUTPUT_DIR = "$CARGO_TARGET_DIR" + '\' + $PLATFORM_TARGET + '\' + "$CARGO_PROFILE"
}
$BUNDLE_ID = "dev.warp.$app_name"
# Update parameters based on the target release channel.
#
# APP_NAME here must match the value used in Rust as the
# application name; see app/src/channel.rs.
#
# WARP_BIN is the name of the binary produced by cargo;
# BINARY_NAME is the desired name of the binary in the final package.
if ("$CHANNEL" -eq 'local') {
$WARP_BIN = 'warp'
$BINARY_NAME = 'warp.exe'
$APP_NAME = 'WarpLocal'
$FEATURES = "$FEATURES,nld_improvements"
} elseif ("$CHANNEL" -eq 'dev') {
$WARP_BIN = 'dev'
$BINARY_NAME = 'dev.exe'
$APP_NAME = 'WarpDev'
$FEATURES = "$FEATURES,agent_mode_debug,nld_improvements"
} elseif ("$CHANNEL" -eq 'preview') {
$WARP_BIN = 'preview'
$BINARY_NAME = 'preview.exe'
$APP_NAME = 'WarpPreview'
$FEATURES = "$FEATURES,preview_channel,nld_improvements"
} elseif ("$CHANNEL" -eq 'stable') {
$WARP_BIN = 'stable'
$BINARY_NAME = 'warp.exe'
$APP_NAME = 'Warp'
# TODO(vorporeal): Remove this once we get tests passing with this default enabled.
$FEATURES = "$FEATURES,nld_improvements"
}
$BINARY_PATH = "$CARGO_TARGET_OUTPUT_DIR\$BINARY_NAME"
$BUNDLE_ID = "dev.warp.$APP_NAME"
$INSTALLER_OUTPUT_DIR = "$WINDOWS_INSTALLER_DIR\Output"
$INSTALLER_NAME = "$($APP_NAME)$($FILE_ENDING)"
$INSTALLER_PATH = "$($INSTALLER_OUTPUT_DIR)\$($INSTALLER_NAME).exe"
$PDB_PATH = "$CARGO_TARGET_OUTPUT_DIR\$WARP_BIN.pdb"
# The CARGO_FULL_PROFILE environment variable is read by the `cargo` build
# script (`app/build.rs`) to determine where to place `conpty.dll`.
if ($DEBUG_BUILD) {
$env:CARGO_FULL_PROFILE = 'debug'
} else {
$env:CARGO_FULL_PROFILE = $CARGO_PROFILE
}
# 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) {
cargo check -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" --target $PLATFORM_TARGET
if (-Not $?) {
Write-Error "Failed to verify Warp $WARP_BIN compilation with profile $CARGO_PROFILE"
exit 1
}
exit 0
}
if (-Not $SKIP_BUILD_BINARY) {
Write-Output "Building Warp for channel $CHANNEL and bundle id $BUNDLE_ID"
$env:CARGO_BIN_NAME = $CHANNEL
$env:WARP_APP_NAME = $APP_NAME
cargo build -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES" --target $PLATFORM_TARGET
if (-Not $?) {
Write-Error "Failed to build Warp $WARP_BIN binary with profile $CARGO_PROFILE"
exit 1
}
# If we desire an executable name different from the cargo bin, rename it.
if ("$WARP_BIN.exe" -ne $BINARY_NAME) {
$binarySource = "$CARGO_TARGET_OUTPUT_DIR\$WARP_BIN.exe"
Write-Output "Renaming executable $WARP_BIN.exe to $BINARY_NAME"
Move-Item -Path "$binarySource" -Destination "$BINARY_PATH" -Force
}
}
if ($SKIP_BUILD_INSTALLER) {
# If this is being run within a GitHub action, set an output variable with the
# location of the binary so it can be referenced by subsequent actions.
if ($env:GITHUB_ACTIONS -eq 'true') {
Write-Output '::echo::on'
"target_profile_dir=$CARGO_TARGET_OUTPUT_DIR" >> "$env:GITHUB_OUTPUT"
"binary_path=$BINARY_PATH" >> "$env:GITHUB_OUTPUT"
Write-Output '::echo::off'
}
exit 0
}
Write-Output "Built for $ARCH with executable at $BINARY_PATH"
# Prepare bundled resources
$BUNDLED_RESOURCES_DIR = "$CARGO_TARGET_OUTPUT_DIR\resources"
Write-Output "Preparing bundled resources..."
& "$WINDOWS_INSTALLER_DIR\prepare_bundled_resources.ps1" -DestinationDir "$BUNDLED_RESOURCES_DIR" -Channel "$CHANNEL" -CargoProfile "$CARGO_PROFILE"
if (-Not $?) {
Write-Error "Failed to prepare bundled resources"
exit 1
}
Write-Output 'Building Warp installer'
$ISCC_ARGS = @(
"$WINDOWS_INSTALLER_DIR\windows-installer.iss",
"/DReleaseChannel=$CHANNEL",
"/DMyAppExeName=$BINARY_NAME",
"/DTargetProfileDir=$CARGO_TARGET_OUTPUT_DIR",
"/DMyAppName=$APP_NAME",
"/DMyAppVersion=$env:GIT_RELEASE_TAG",
"/DArch=$ARCH",
"/DOutputName=$INSTALLER_NAME"
)
# Also accept the sign tool command via env var
if (-not $SIGN_TOOL_CMD -and $env:SIGN_TOOL_CMD) {
$SIGN_TOOL_CMD = $env:SIGN_TOOL_CMD
}
if ($SIGN_TOOL_CMD) {
$ISCC_ARGS += '/DSIGN_TOOL=1'
$ISCC_ARGS += "/Scodesign=$SIGN_TOOL_CMD"
}
& ISCC @ISCC_ARGS
if (-Not $?) {
Write-Error "Failed to build $APP_NAME installer"
exit 1
}
# If this is being run within a GitHub action, set an output variable with the
# location of the installer so it can be referenced by subsequent actions.
if ($env:GITHUB_ACTIONS -eq 'true') {
Write-Output '::echo::on'
$INSTALLER_PATH = $INSTALLER_PATH -replace '\\', '/'
"installer_path=$INSTALLER_PATH" >> "$env:GITHUB_OUTPUT"
"pdb_file_path=$PDB_PATH" >> "$env:GITHUB_OUTPUT"
Write-Output '::echo::off'
}
+73
View File
@@ -0,0 +1,73 @@
[Code]
{ Adapted from https://stackoverflow.com/a/46609047 }
const
SystemEnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
UserEnvironmentKey = 'Environment';
{ Get the appropriate registry key and root based on install mode. }
procedure GetEnvironmentKeyInfo(var RootKey: Integer; var SubKey: string);
begin
if IsAdminInstallMode then begin
RootKey := HKEY_LOCAL_MACHINE;
SubKey := SystemEnvironmentKey;
end else begin
RootKey := HKEY_CURRENT_USER;
SubKey := UserEnvironmentKey;
end;
end;
{ Add path to environment PATH variable. }
procedure EnvAddPath(Path: string);
var
Paths: string;
RootKey: Integer;
SubKey: string;
begin
{ Get the appropriate registry location }
GetEnvironmentKeyInfo(RootKey, SubKey);
{ Retrieve current path (use empty string if entry not exists) }
if not RegQueryStringValue(RootKey, SubKey, 'Path', Paths)
then Paths := '';
{ Skip if string already found in path }
if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit;
{ Append string to the end of the path variable }
Paths := Paths + ';'+ Path +';'
{ Overwrite (or create if missing) path environment variable }
if RegWriteStringValue(RootKey, SubKey, 'Path', Paths)
then Log(Format('Added [%s] to PATH: [%s]', [Path, Paths]))
else Log(Format('Error adding [%s] to PATH: [%s]', [Path, Paths]));
end;
{ Remove path from environment PATH variable. }
procedure EnvRemovePath(Path: string);
var
Paths: string;
P: Integer;
RootKey: Integer;
SubKey: string;
begin
{ Get the appropriate registry location }
GetEnvironmentKeyInfo(RootKey, SubKey);
{ Skip if registry entry not exists }
if not RegQueryStringValue(RootKey, SubKey, 'Path', Paths) then
exit;
{ Skip if string not found in path }
P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';');
if P = 0 then exit;
{ Update path variable }
Delete(Paths, P - 1, Length(Path) + 1);
{ Overwrite path environment variable }
if RegWriteStringValue(RootKey, SubKey, 'Path', Paths)
then Log(Format('Removed [%s] from PATH: [%s]', [Path, Paths]))
else Log(Format('Error removing [%s] from PATH: [%s]', [Path, Paths]));
end;
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env powershell
#
# Install all dependencies required to build Warp on Windows.
# Install Rust + cargo.
bash (((Get-Location).path) + '\script\install_rust')
# Install various build-time dependencies through cargo.
bash (((Get-Location).path) + '\script\install_cargo_build_deps')
Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1,183 @@
#
# Prepares bundled resources for distribution on Windows.
#
# This script copies resources that should be bundled with Warp into a
# destination directory. It is used by the Windows build script.
#
# Usage:
# prepare_bundled_resources.ps1 <destination_directory>
#
# Arguments:
# destination_directory: The directory where resources should be installed.
# Resources will be copied to subdirectories within
# this path (e.g., $DEST_DIR\skills).
Param(
[Parameter(Mandatory = $true)]
[String]$DestinationDir,
[Parameter(Mandatory = $false)]
[String]$Channel = '',
[Parameter(Mandatory = $false)]
[String]$CargoProfile = ''
)
$ErrorActionPreference = 'Stop'
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = (Get-Item "$ScriptDir\..\.." | Select-Object -ExpandProperty FullName)
$ResourcesSource = Join-Path $RepoRoot 'resources'
# Validate that the source resources directory exists
if (-Not (Test-Path $ResourcesSource -PathType Container)) {
Write-Error "Resources directory not found at $ResourcesSource"
exit 1
}
# Create the destination directory if it doesn't exist
if (-Not (Test-Path $DestinationDir)) {
New-Item -ItemType Directory -Path $DestinationDir -Force | Out-Null
}
# Copy bundled resources
$BundledSource = Join-Path $ResourcesSource 'bundled'
if (Test-Path $BundledSource -PathType Container) {
$BundledDestination = Join-Path $DestinationDir 'bundled'
Write-Output "Copying bundled resources to $BundledDestination"
if (Test-Path $BundledDestination -PathType Container) {
Remove-Item -Path $BundledDestination -Recurse -Force
}
Copy-Item -Path $BundledSource -Destination $BundledDestination -Recurse -Force
} else {
Write-Warning "No bundled directory found at $BundledSource"
}
if ($env:GIT_RELEASE_TAG) {
$VersionMetadataDir = Join-Path (Join-Path $DestinationDir 'bundled') 'metadata'
$VersionMetadataPath = Join-Path $VersionMetadataDir 'version.json'
Write-Output "Writing bundled Warp version metadata to $VersionMetadataPath"
if (-Not (Test-Path $VersionMetadataDir -PathType Container)) {
New-Item -ItemType Directory -Path $VersionMetadataDir -Force | Out-Null
}
@{ warp_version = $env:GIT_RELEASE_TAG } |
ConvertTo-Json |
Set-Content -Path $VersionMetadataPath -Encoding utf8
}
# Copy channel-gated skills matching the current release channel.
$GatedSource = Join-Path (Join-Path $RepoRoot 'resources') 'channel-gated-skills'
$DestSkills = Join-Path (Join-Path $DestinationDir 'bundled') 'skills'
if ($Channel -and (Test-Path $GatedSource -PathType Container)) {
Write-Output "Copying channel-gated skills for channel '$Channel'..."
# Error out if a stable/ gate directory exists.
$StableDir = Join-Path $GatedSource 'stable'
if (Test-Path $StableDir -PathType Container) {
Write-Error "Found a 'stable/' directory in $GatedSource. The stable channel does not use gated skills. Move stable-ready skills to resources/skills/ instead."
exit 1
}
# Gate labels ordered from most-inclusive to least-inclusive.
$GateOrder = @('dogfood', 'preview')
# Map the release channel to its gate label.
switch ($Channel) {
'local' { $Gate = 'dogfood' }
'dev' { $Gate = 'dogfood' }
'preview' { $Gate = 'preview' }
default {
Write-Output " Channel '$Channel' has no gated skills, skipping"
$Gate = $null
}
}
if ($Gate) {
# Build the set of included gates (progressive: this gate and all after it).
$GateIndex = [array]::IndexOf($GateOrder, $Gate)
$IncludedGates = $GateOrder[$GateIndex..($GateOrder.Length - 1)]
foreach ($GateDir in Get-ChildItem -Path $GatedSource -Directory | Sort-Object Name) {
if ($GateDir.Name -notin $IncludedGates) {
$Skills = (Get-ChildItem -Path $GateDir.FullName -Directory | Sort-Object Name | ForEach-Object { $_.Name }) -join ', '
Write-Output " Skipping gate '$($GateDir.Name)' (channel '$Channel') - would include: $Skills"
continue
}
foreach ($SkillDir in Get-ChildItem -Path $GateDir.FullName -Directory | Sort-Object Name) {
$Dest = Join-Path $DestSkills $SkillDir.Name
Write-Output " Copying gated skill: $($SkillDir.Name) (gate: $($GateDir.Name))"
Copy-Item -Path $SkillDir.FullName -Destination $Dest -Recurse -Force
}
}
}
}
# Generate third-party license attribution.
#
# Additional (non-Cargo) third-party license files to include in the output.
# When adding a new third-party component to the bundle, add its license file
# to the repo alongside the component and add an entry here.
# Cross-platform components:
$AdditionalLicenses = @(
@{ Name = 'Hack Font'; License = 'MIT'; Path = 'app\assets\bundled\fonts\hack\LICENSE.md' },
@{ Name = 'Roboto Font'; License = 'SIL Open Font License'; Path = 'app\assets\bundled\fonts\roboto\LICENSE.txt' },
@{ Name = 'bash-preexec'; License = 'MIT'; Path = 'app\assets\bundled\bootstrap\bash-preexec-LICENSE.md' },
@{ Name = 'Claude API Skill'; License = 'Apache-2.0'; Path = 'resources\bundled\skills\claude-api\LICENSE.txt' },
@{ Name = 'rudder-sdk-rust'; License = 'MIT'; Path = 'app\src\server\telemetry\LICENSE-RUDDER-SDK-RUST.txt' },
@{ Name = 'Windows Terminal'; License = 'MIT'; Path = 'app\assets\windows\LICENSE-WINDOWS-TERMINAL' },
@{ Name = 'GitHub Desktop'; License = 'MIT'; Path = 'app\src\code_review\GITHUB-DESKTOP-LICENSE' }
)
# Windows-only components:
$AdditionalLicenses += @(
@{ Name = 'OpenConsole / ConPTY (Windows Terminal)'; License = 'MIT'; Path = 'app\assets\windows\LICENSE-WINDOWS-TERMINAL' },
@{ Name = 'DirectX Shader Compiler'; License = 'NCSA'; Path = 'app\assets\windows\LICENSE-DXC' }
)
$LicensesOutput = Join-Path $DestinationDir 'THIRD_PARTY_LICENSES.txt'
Write-Output "Generating third-party licenses at $LicensesOutput"
cargo about generate --workspace --manifest-path "$RepoRoot\Cargo.toml" -c "$RepoRoot\about.toml" -o "$LicensesOutput" "$RepoRoot\about.hbs"
if (-Not $?) {
Write-Error 'Failed to generate third-party licenses'
exit 1
}
# Append additional (non-Cargo) third-party licenses.
foreach ($entry in $AdditionalLicenses) {
$LicenseFile = Join-Path $RepoRoot $entry.Path
if (-Not (Test-Path $LicenseFile)) {
Write-Error "License file not found: $LicenseFile"
exit 1
}
Add-Content -Path $LicensesOutput -Value ''
Add-Content -Path $LicensesOutput -Value "$($entry.Name) ($($entry.License))"
Add-Content -Path $LicensesOutput -Value ('-' * 80)
Get-Content -Path $LicenseFile | Add-Content -Path $LicensesOutput
Add-Content -Path $LicensesOutput -Value ''
}
# Generate settings JSON schema unless explicitly skipped.
if ($env:SKIP_SETTINGS_SCHEMA -ne '1') {
$SchemaOutput = Join-Path $DestinationDir 'settings_schema.json'
Write-Output "Generating settings schema at $SchemaOutput"
$SchemaCmd = @('run')
if ($CargoProfile) {
$SchemaCmd += @('--profile', $CargoProfile)
}
$SchemaCmd += @('--manifest-path', (Join-Path $RepoRoot 'Cargo.toml'), '--bin', 'generate_settings_schema', '--')
if ($Channel) {
$SchemaCmd += @('--channel', $Channel)
}
$SchemaCmd += $SchemaOutput
& cargo @SchemaCmd
if (-Not $?) {
Write-Error 'Failed to generate settings schema'
exit 1
}
}
Write-Output "Successfully prepared bundled resources in $DestinationDir"
+250
View File
@@ -0,0 +1,250 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#include "environment.iss"
#define MyAppPublisher "Denver Technologies, Inc."
#define MyAppURL "https://www.warp.dev/"
#ifndef MyAppName
#define MyAppName "WarpDev"
#endif
#ifndef MyAppVersion
#define MyAppVersion "0.1.0"
#endif
#ifndef MyAppExeName
#define MyAppExeName "dev.exe"
#endif
#ifndef ReleaseChannel
#define ReleaseChannel "dev"
#endif
#ifndef TargetProfileDir
#define TargetProfileDir "target\release-lto-debug_assertions"
#endif
#define AssetsDir "..\..\app\assets\windows"
// The mutex name must match what the Rust app creates in single_instance_manager.rs:
#define ChannelPascalCase \
(ReleaseChannel == "stable") ? "Stable" : \
((ReleaseChannel == "dev") ? "Dev" : \
((ReleaseChannel == "preview") ? "Preview" : \
((ReleaseChannel == "local") ? "Local" : \
((ReleaseChannel == "integration") ? "Integration" : \
"Unknown"))))
#define AppMutexName "Local\Warp" + ChannelPascalCase + "_SingleInstance"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId=warp-terminal-{#ReleaseChannel}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
UninstallDisplayName={#MyAppName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
ArchitecturesAllowed={#Arch}
ArchitecturesInstallIn64BitMode={#Arch}
DisableProgramGroupPage=yes
; The following line defaults the installer to use non administrative install mode (install for current user only).
PrivilegesRequired=lowest
; Allow the user to choose administrative install mode (install for all users).
PrivilegesRequiredOverridesAllowed=dialog
OutputBaseFilename={#OutputName}
Compression=lzma
SolidCompression=yes
WizardStyle=modern
WizardSmallImageFile="installer-images\warp-logo.bmp"
WizardImageFile="installer-images\warp-banner.bmp"
SetupIconFile="..\..\app\channels\{#ReleaseChannel}\icon\no-padding\icon.ico"
UninstallDisplayIcon="{app}\icon.ico"
; Force close previous Warp if it hasn't shut down yet.
; In the update flow we already warn the user if they have something running and make them confirm
; before running this installer. Therefore, we are good to force close Warp without fear of losing
; unsaved work.
; VSCode does something similar:
; https://github.com/microsoft/vscode/blob/aac9914f93551f894b8df1e4680bd847e7636be3/build/win32/code.iss#L41
CloseApplications=force
; For manual installs: if Warp is running, show a dialog prompting the user to close it
; before Setup proceeds. Returned empty for background updates so the check is skipped.
; TODO(andy) uncomment this after the 4/22 release
;AppMutex={code:GetAppMutex}
SetupMutex={#AppMutexName}Setup
; Version 1809 / Build 18362 is required for ConPTY. See https://github.com/microsoft/vscode-docs/blob/9d736b662fdde3fed17d8bc2ed70bfea4ae20636/docs/supporting/troubleshoot-terminal-launch.md?plain=1#L66/
MinVersion=10.0.18362
; Tell Windows Explorer to reload the environment so that path changes take effect.
ChangesEnvironment=true
; Sign the setup engine and uninstaller so that the temporary bootstrapper
; extracted to %TEMP% is Authenticode-signed. This prevents Microsoft Defender
; ASR rule D4F940AB from blocking the installer in enterprise environments.
; The sign tool command is supplied via ISCC /S on the command line; when not
; defined (e.g. local dev builds) signing is skipped.
#ifdef SIGN_TOOL
SignTool=codesign
SignedUninstaller=yes
#endif
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
[Files]
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
Source: "{#TargetProfileDir}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#AssetsDir}\{#Arch}\conpty.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#AssetsDir}\{#Arch}\OpenConsole.exe"; DestDir: "{app}\{#Arch}"; Flags: ignoreversion
Source: "..\..\app\channels\{#ReleaseChannel}\icon\no-padding\icon.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#AssetsDir}\{#Arch}\vcruntime140.dll"; DestDir: "{app}"
Source: "{#AssetsDir}\{#Arch}\vcruntime140_1.dll"; DestDir: "{app}"
Source: "{#AssetsDir}\{#Arch}\msvcp140.dll"; DestDir: "{app}"
Source: "..\..\app\assets\bundled\bootstrap\pwsh.ps1"; DestDir: "{app}"
Source: "{#AssetsDir}\{#Arch}\dxcompiler.dll"; DestDir: "{app}"
Source: "{#AssetsDir}\{#Arch}\dxil.dll"; DestDir: "{app}"
Source: "{#TargetProfileDir}\resources\*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs
[Registry]
Root: HKCU; Subkey: "SOFTWARE\Warp.dev\{#MyAppName}"; Flags: uninsdeletekey
; cleanup "Open Warp Here" registry entries
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}"; Flags: deletekey
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}"; Flags: deletekey
; Add "Open Warp in new tab" to directory context menu
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}Tab"; ValueType: string; ValueName: ""; ValueData: "Open {#MyAppName} in new tab"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}Tab"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\icon.ico"
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}Tab\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""{#MyAppName}://action/new_tab?path=%1"""
; Add "Open Warp in new tab" to directory background context menu
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}Tab"; ValueType: string; ValueName: ""; ValueData: "Open {#MyAppName} in new tab"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}Tab"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\icon.ico"
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}Tab\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""{#MyAppName}://action/new_tab?path=%V"""
; Add "Open Warp in new window" to directory context menu
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}Window"; ValueType: string; ValueName: ""; ValueData: "Open {#MyAppName} in new window"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}Window"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\icon.ico"
Root: HKA; Subkey: "Software\Classes\Directory\shell\{#MyAppName}Window\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""{#MyAppName}://action/new_window?path=%1"""
; Add "Open Warp in new window" to directory background context menu
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}Window"; ValueType: string; ValueName: ""; ValueData: "Open {#MyAppName} in new window"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}Window"; ValueType: string; ValueName: "Icon"; ValueData: "{app}\icon.ico"
Root: HKA; Subkey: "Software\Classes\Directory\Background\shell\{#MyAppName}Window\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""{#MyAppName}://action/new_window?path=%V"""
[Tasks]
Name: addToPath; Description: "Add Warp to PATH"
[UninstallDelete]
Type: filesandordirs; Name: "{userappdata}\warp\{#MyAppName}"
Type: filesandordirs; Name: "{localappdata}\warp\{#MyAppName}"
Type: filesandordirs; Name: "{app}\bin"
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\icon.ico"; AppUserModelID: "dev.warp.{#MyAppName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\icon.ico"; AppUserModelID: "dev.warp.{#MyAppName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: postinstall runhidden nowait
[Code]
function IsNotStable(): Boolean;
begin
#if ReleaseChannel == "stable"
Result := False;
#else
Result := True;
#endif
end;
{ Returns true when the installer was launched by Warp's auto-update code.
The auto-update path passes /update=1 on the command line and /NOCLOSEAPPLICATIONS
so that the installer does not forcibly kill the running Warp process. Instead we
wait for Warp to exit naturally by polling the app mutex below. }
function IsBackgroundUpdate(): Boolean;
begin
Result := ExpandConstant('{param:update|false}') <> 'false';
end;
{ For background updates, return an empty mutex name so that Inno Setup skips its
built-in "application is running" dialog - we handle the wait ourselves. For manual
installs, return the real mutex name so the user is prompted to close Warp first. }
function GetAppMutex(Value: string): string;
begin
if IsBackgroundUpdate() then
Result := ''
else
Result := '{#AppMutexName}';
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
BinDir: string;
CmdScriptName: string;
CmdScriptPath: string;
CmdScriptContent: string;
WaitCounter: Integer;
ResultCode: Integer;
begin
{ Background update: the installer was launched while Warp was still running.
We passed /NOCLOSEAPPLICATIONS so Inno won't kill it. Wait here - before any
files are touched - for Warp to release its single-instance mutex, which
happens as part of normal process exit. }
if CurStep = ssInstall then
begin
if IsBackgroundUpdate() then
begin
Log('Background update: waiting for Warp to exit (mutex: {#AppMutexName})...');
WaitCounter := 0;
while CheckForMutexes('{#AppMutexName}') and (WaitCounter < 30) do
begin
Sleep(500);
WaitCounter := WaitCounter + 1;
end;
if CheckForMutexes('{#AppMutexName}') then
begin
Log('Warp mutex still held after timeout; force-killing remaining processes.');
{ Kill by image name. {#MyAppExeName} (e.g. warp.exe, dev.exe) is unique
enough that collateral damage is not a concern. OpenConsole.exe is NOT
killed by name because it is shared with Windows Terminal; instead we
rely on Warp's Job Object (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) to
cascade-kill any child OpenConsole.exe processes when warp.exe dies. }
Exec('taskkill.exe', '/f /im {#MyAppExeName}', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if ResultCode <> 0 then
Log('force-kill failed for {#MyAppExeName} (exit code: ' + IntToStr(ResultCode) + ')');
Sleep(1000);
end
else
Log('Warp has exited; proceeding with file installation.');
end;
end;
{ After a successful install, write a helper script for running the Warp CLI. }
{ We use this to add a "warp-" prefix (e.g. "warp-preview.cmd" vs. "preview.exe") }
if CurStep = ssPostInstall then begin
{ Add Warp to PATH if requested }
if IsTaskSelected('addToPath') then
EnvAddPath(ExpandConstant('{app}\bin'));
BinDir := ExpandConstant('{app}\bin');
if not DirExists(BinDir) then
CreateDir(BinDir);
{ Determine the channel-specific script name. }
#if ReleaseChannel == "stable"
CmdScriptName := 'oz.cmd'
#else
CmdScriptName := 'oz-{#ReleaseChannel}.cmd';
#endif
{ Create the helper CMD script }
CmdScriptPath := BinDir + '\' + CmdScriptName;
CmdScriptContent := '@echo off' + #13#10 +
'set "WARP_CLI_MODE=1"' + #13#10 +
'"' + ExpandConstant('{app}\{#MyAppExeName}') + '" %*' + #13#10;
SaveStringToFile(CmdScriptPath, CmdScriptContent, False);
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
EnvRemovePath(ExpandConstant('{app}\bin'));
end;