Files
zed/script/bundle-mac
Conrad Irwin 4da5675920 Re-use the existing bundle steps for nightly too (#41699)
One of the reasons we didn't spot that we were missing the telemetry env
vars for the production builds was that nightly (which was working) had
its own set of build steps. This re-uses those and pushes the env vars
down from the workflow to the job.

It also fixes nightly releases to upload all-in-one go so that all
platforms update in sync.

Closes #41655

Release Notes:

- N/A
2025-11-03 12:29:22 -07:00

314 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
source script/lib/blob-store.sh
build_flag="--release"
target_dir="release"
open_result=false
local_install=false
can_code_sign=false
# This must match the team in the provisioning profile.
IDENTITY="Zed Industries, Inc."
APPLE_NOTARIZATION_TEAM="MQ55VZLNZQ"
# Function for displaying help info
help_info() {
echo "
Usage: ${0##*/} [options] [architecture=host]
Build the application bundle for macOS.
Options:
-d Compile in debug mode
-o Open dir with the resulting DMG or launch the app itself in local mode.
-i Install the resulting DMG into /Applications in local mode. Noop without -l.
-h Display this help and exit.
"
}
while getopts 'dloih' flag
do
case "${flag}" in
o) open_result=true;;
d)
export CARGO_INCREMENTAL=true
export CARGO_BUNDLE_SKIP_BUILD=true
build_flag="";
target_dir="debug"
;;
i) local_install=true;;
h)
help_info
exit 0
;;
esac
done
shift $((OPTIND-1))
# Get release channel
pushd crates/zed
channel=$(<RELEASE_CHANNEL)
export ZED_RELEASE_CHANNEL="${channel}"
popd
export ZED_BUNDLE=true
cargo_bundle_version=$(cargo -q bundle --help 2>&1 | head -n 1 || echo "")
if [ "$cargo_bundle_version" != "cargo-bundle v0.6.1-zed" ]; then
cargo install cargo-bundle --git https://github.com/zed-industries/cargo-bundle.git --branch zed-deploy
fi
# Deal with versions of macOS that don't include libstdc++ headers
export CXXFLAGS="-stdlib=libc++"
version_info=$(rustc --version --verbose)
host_line=$(echo "$version_info" | grep host)
target_triple=${host_line#*: }
if [[ $# -gt 0 && -n "$1" ]]; then
target_triple="$1"
fi
arch_suffix=""
if [[ "$target_triple" = "x86_64-apple-darwin" ]]; then
arch_suffix="x86_64"
elif [[ "$target_triple" = "aarch64-apple-darwin" ]]; then
arch_suffix="aarch64"
else
echo "Unsupported architecture $target_triple"
exit 1
fi
# Generate the licenses first, so they can be baked into the binaries
script/generate-licenses
rustup target add $target_triple
echo "Compiling zed binaries"
cargo build ${build_flag} --package zed --package cli --target $target_triple
# Build remote_server in separate invocation to prevent feature unification from other crates
# from influencing dynamic libraries required by it.
cargo build ${build_flag} --package remote_server --target $target_triple
echo "Creating application bundle"
pushd crates/zed
cp Cargo.toml Cargo.toml.backup
sed \
-i.backup \
"s/package.metadata.bundle-${channel}/package.metadata.bundle/" \
Cargo.toml
app_path=$(cargo bundle ${build_flag} --target $target_triple --select-workspace-root | xargs)
mv Cargo.toml.backup Cargo.toml
popd
echo "Bundled ${app_path}"
if [[ -n "${MACOS_CERTIFICATE:-}" && -n "${MACOS_CERTIFICATE_PASSWORD:-}" && -n "${APPLE_NOTARIZATION_KEY:-}" && -n "${APPLE_NOTARIZATION_KEY_ID:-}" && -n "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then
can_code_sign=true
echo "Setting up keychain for code signing..."
security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" zed.keychain || echo ""
security default-keychain -s zed.keychain
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" zed.keychain
echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/zed-certificate.p12
security import /tmp/zed-certificate.p12 -k zed.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
rm /tmp/zed-certificate.p12
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" zed.keychain
function cleanup() {
echo "Cleaning up keychain"
security default-keychain -s login.keychain
security delete-keychain zed.keychain
}
trap cleanup EXIT
fi
GIT_VERSION="v2.43.3"
GIT_VERSION_SHA="fa29823"
function download_and_unpack() {
local url=$1
local path_to_unpack=$2
local target_path=$3
temp_dir=$(mktemp -d)
if ! command -v curl &> /dev/null; then
echo "curl is not installed. Please install curl to continue."
exit 1
fi
curl --silent --fail --location "$url" | tar -xvz -C "$temp_dir" -f - $path_to_unpack
mv "$temp_dir/$path_to_unpack" "$target_path"
rm -rf "$temp_dir"
}
function download_git() {
local architecture=$1
local target_binary=$2
tmp_dir=$(mktemp -d)
pushd "$tmp_dir"
case "$architecture" in
aarch64-apple-darwin)
download_and_unpack "https://github.com/desktop/dugite-native/releases/download/${GIT_VERSION}/dugite-native-${GIT_VERSION}-${GIT_VERSION_SHA}-macOS-arm64.tar.gz" bin/git ./git
;;
x86_64-apple-darwin)
download_and_unpack "https://github.com/desktop/dugite-native/releases/download/${GIT_VERSION}/dugite-native-${GIT_VERSION}-${GIT_VERSION_SHA}-macOS-x64.tar.gz" bin/git ./git
;;
*)
echo "Unsupported architecture: $architecture"
exit 1
;;
esac
popd
mv "${tmp_dir}/git" "${target_binary}"
rm -rf "$tmp_dir"
}
function sign_app_binaries() {
rm -rf "${app_path}/Contents/Frameworks"
mkdir -p "${app_path}/Contents/Frameworks"
echo "Downloading git binary"
download_git "${target_triple}" "${app_path}/Contents/MacOS/git"
# Note: The app identifier for our development builds is the same as the app identifier for nightly.
cp crates/zed/contents/$channel/embedded.provisionprofile "${app_path}/Contents/"
if [[ $can_code_sign = true ]]; then
echo "Code signing binaries"
# sequence of codesign commands modeled after this example: https://developer.apple.com/forums/thread/701514
/usr/bin/codesign --deep --force --timestamp --options runtime --sign "$IDENTITY" "${app_path}/Contents/MacOS/cli" -v
/usr/bin/codesign --deep --force --timestamp --options runtime --sign "$IDENTITY" "${app_path}/Contents/MacOS/git" -v
/usr/bin/codesign --deep --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "$IDENTITY" "${app_path}/Contents/MacOS/zed" -v
/usr/bin/codesign --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "$IDENTITY" "${app_path}" -v
else
echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_KEY, APPLE_NOTARIZATION_KEY_ID, APPLE_NOTARIZATION_ISSUER_ID"
echo "====== WARNING ======"
echo "This bundle is being signed without all entitlements, some features (e.g. universal links) will not work"
echo "====== WARNING ======"
# NOTE: if you need to test universal links you have a few paths forward:
# - create a PR and tag it with the `run-bundling` label, and download the .dmg file from there.
# - get a signing key for the MQ55VZLNZQ team from Nathan.
# - create your own signing key, and update references to MQ55VZLNZQ to your own team ID
# then comment out this line.
cat crates/zed/resources/zed.entitlements | sed '/com.apple.developer.associated-domains/,+1d' > "${app_path}/Contents/Resources/zed.entitlements"
codesign --force --deep --entitlements "${app_path}/Contents/Resources/zed.entitlements" --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v
fi
if [[ "$target_dir" = "debug" ]]; then
if [ "$open_result" = true ]; then
open "$app_path"
else
echo "Created application bundle:"
echo "$app_path"
fi
exit 0
fi
bundle_name=$(basename "$app_path")
if [ "$local_install" = true ]; then
rm -rf "/Applications/$bundle_name"
mv "$app_path" "/Applications/$bundle_name"
echo "Installed application bundle: /Applications/$bundle_name"
if [ "$open_result" = true ]; then
echo "Opening /Applications/$bundle_name"
open "/Applications/$bundle_name"
fi
else
dmg_target_directory="target/${target_triple}/${target_dir}"
dmg_source_directory="${dmg_target_directory}/dmg"
dmg_file_path="${dmg_target_directory}/Zed-${arch_suffix}.dmg"
xcode_bin_dir_path="$(xcode-select -p)/usr/bin"
rm -rf ${dmg_source_directory}
mkdir -p ${dmg_source_directory}
mv "${app_path}" "${dmg_source_directory}"
notarization_key_file=$(mktemp)
echo "Adding symlink to /Applications to ${dmg_source_directory}"
ln -s /Applications ${dmg_source_directory}
echo "Creating final DMG at ${dmg_file_path} using ${dmg_source_directory}"
hdiutil create -volname Zed -srcfolder "${dmg_source_directory}" -ov -format UDZO "${dmg_file_path}"
# If someone runs this bundle script locally, a symlink will be placed in `dmg_source_directory`.
# This symlink causes CPU issues with Zed if the Zed codebase is the project being worked on, so we simply remove it for now.
echo "Removing symlink to /Applications from ${dmg_source_directory}"
rm ${dmg_source_directory}/Applications
echo "Adding license agreement to DMG"
npm install --global dmg-license minimist
dmg-license script/terms/terms.json "${dmg_file_path}"
if [[ $can_code_sign = true ]]; then
echo "Notarizing DMG with Apple"
/usr/bin/codesign --deep --force --timestamp --options runtime --sign "$IDENTITY" "$(pwd)/${dmg_file_path}" -v
echo "$APPLE_NOTARIZATION_KEY" > "$notarization_key_file"
"${xcode_bin_dir_path}/notarytool" submit --wait --key "$notarization_key_file" --key-id "$APPLE_NOTARIZATION_KEY_ID" --issuer "$APPLE_NOTARIZATION_ISSUER_ID" "${dmg_file_path}"
rm "$notarization_key_file"
"${xcode_bin_dir_path}/stapler" staple "${dmg_file_path}"
fi
if [ "$open_result" = true ]; then
open $dmg_target_directory
fi
fi
}
function sign_binary() {
local binary_path=$1
if [[ $can_code_sign = true ]]; then
echo "Code signing executable $binary_path"
/usr/bin/codesign --deep --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "$IDENTITY" "${binary_path}" -v
fi
}
function upload_debug_symbols() {
if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
echo "Uploading zed debug symbols to sentry..."
exe_path="target/${target_triple}/release/Zed"
if ! dsymutil --flat "target/${target_triple}/${target_dir}/zed" 2> target/dsymutil.log; then
echo "dsymutil failed"
cat target/dsymutil.log
exit 1
fi
if ! dsymutil --flat "target/${target_triple}/${target_dir}/remote_server" 2> target/dsymutil.log; then
echo "dsymutil failed"
cat target/dsymutil.log
exit 1
fi
# note: this uploads the unstripped binary which is needed because it contains
# .eh_frame data for stack unwinding. see https://github.com/getsentry/symbolic/issues/783
sentry-cli debug-files upload --include-sources --wait -p zed -o zed-dev \
"target/${target_triple}/${target_dir}/zed.dwarf" \
"target/${target_triple}/${target_dir}/remote_server.dwarf"
else
echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
fi
}
upload_debug_symbols
cp target/${target_triple}/${target_dir}/zed "${app_path}/Contents/MacOS/zed"
cp target/${target_triple}/${target_dir}/cli "${app_path}/Contents/MacOS/cli"
sign_app_binaries
sign_binary "target/$target_triple/release/remote_server"
gzip -f --stdout --best target/$target_triple/release/remote_server > target/zed-remote-server-macos-$arch_suffix.gz