WIP: Notarize Mac builds in parallel

Piotr Osiewicz created

Change summary

.github/workflows/ci.yml |  53 +++++-
script/build-mac         | 155 ++++++++++++++++++++
script/bundle-mac        | 318 +----------------------------------------
script/notarize-mac      | 255 +++++++++++++++++++++++++++++++++
4 files changed, 459 insertions(+), 322 deletions(-)

Detailed changes

.github/workflows/ci.yml 🔗

@@ -511,27 +511,16 @@ jobs:
           fi
           exit $RET_CODE
 
-  bundle-mac:
+  release-build-mac:
     timeout-minutes: 120
-    name: Create a macOS bundle
+    name: Build optimized macOS artifacts
     runs-on:
       - self-mini-macos
     if: |
       ( startsWith(github.ref, 'refs/tags/v')
       || contains(github.event.pull_request.labels.*.name, 'run-bundling') )
     needs: [macos_tests]
-    env:
-      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
-      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
-      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
-      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
-      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
     steps:
-      - name: Install Node
-        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
-        with:
-          node-version: "18"
-
       - name: Setup Sentry CLI
         uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
         with:
@@ -568,7 +557,7 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
       - name: Create macOS app bundle
-        run: script/bundle-mac
+        run: script/build-mac
 
       - name: Rename binaries
         if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
@@ -590,6 +579,42 @@ jobs:
           name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
           path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
 
+  notarize-mac:
+    name: Notarize macOS binaries
+    timeout-minutes: 120
+    runs-on:
+      - self-mini-macos
+    if: |
+      ( startsWith(github.ref, 'refs/tags/v')
+      || contains(github.event.pull_request.labels.*.name, 'run-bundling') )
+    needs: [release-build-mac]
+    strategy:
+      matrix:
+        target: [aarch64-apple-darwin, x86_64-apple-darwin]
+    env:
+      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+      APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
+      APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
+      APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
+
+    steps:
+      - name: Install Node
+        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+        with:
+          node-version: "18"
+      - name: Notarize ${{ matrix.target }}
+        run: ./script/notarize-mac ${{ matrix.target }}
+
+  bundle-mac:
+    name: Create macOS release
+    timeout-minutes: 60
+    needs: [notarize-mac]
+    runs-on: self-mini-macos
+    if: |
+      ( startsWith(github.ref, 'refs/tags/v')
+      || contains(github.event.pull_request.labels.*.name, 'run-bundling') )
+    steps:
       - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
         name: Upload app bundle to release
         if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}

script/build-mac 🔗

@@ -0,0 +1,155 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+source script/lib/blob-store.sh
+
+build_flag="--release"
+target_dir="release"
+local_arch=false
+local_only=false
+local_install=false
+bundle_name=""
+
+# 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] [bundle_name]
+Build the application bundle for macOS.
+
+Options:
+  -d    Compile in debug mode
+  -l    Compile for local architecture only.
+  -i    Install the resulting DMG into /Applications in local mode. Noop without -l.
+  -h    Display this help and exit.
+  "
+}
+
+function prepare_binaries() {
+    local architecture=$1
+    local app_path=$2
+
+    cp target/${architecture}/${target_dir}/zed "${app_path}/Contents/MacOS/zed"
+    cp target/${architecture}/${target_dir}/cli "${app_path}/Contents/MacOS/cli"
+}
+
+while getopts 'dlih' flag
+do
+    case "${flag}" in
+        d)
+            export CARGO_INCREMENTAL=true
+            export CARGO_BUNDLE_SKIP_BUILD=true
+            build_flag="";
+            target_dir="debug"
+            ;;
+        l)
+            export CARGO_INCREMENTAL=true
+            export CARGO_BUNDLE_SKIP_BUILD=true
+            local_arch=true
+            local_only=true
+            ;;
+        i) local_install=true;;
+        h)
+           help_info
+           exit 0
+           ;;
+    esac
+done
+
+shift $((OPTIND-1))
+
+if [[ $# -gt 0 ]]; then
+    if [ "$1" ]; then
+        bundle_name=$1
+    fi
+fi
+
+# 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)
+local_target_triple=${host_line#*: }
+
+# Generate the licenses first, so they can be baked into the binaries
+script/generate-licenses
+
+if [ "$local_arch" = true ]; then
+    echo "Building for local target only."
+    cargo build ${build_flag} --package zed --package cli --package remote_server
+else
+    rustup target add aarch64-apple-darwin
+    rustup target add x86_64-apple-darwin
+
+    echo "Compiling zed binaries"
+    cargo build ${build_flag} --package zed --package cli --target aarch64-apple-darwin --target x86_64-apple-darwin
+    # 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 aarch64-apple-darwin --target x86_64-apple-darwin
+fi
+
+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
+
+if [ "$local_arch" = true ]; then
+    app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs)
+else
+    app_path_x64=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs)
+    app_path_aarch64=$(cargo bundle ${build_flag} --target aarch64-apple-darwin --select-workspace-root | xargs)
+    app_path=$app_path_x64
+fi
+
+if [ "$local_arch" = true ]; then
+    cp -R target/${target_dir}/cli "${app_path}/Contents/MacOS/"
+else
+    prepare_binaries "aarch64-apple-darwin" "$app_path_aarch64"
+    prepare_binaries "x86_64-apple-darwin" "$app_path_x64"
+fi
+
+mv Cargo.toml.backup Cargo.toml
+popd
+echo "Built ${app_path}"
+
+function upload_debug_info() {
+    architecture=$1
+    if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
+        echo "Uploading zed debug symbols to sentry..."
+        # 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/${architecture}/${target_dir}/zed" \
+            "target/${architecture}/${target_dir}/remote_server" \
+            "target/${architecture}/${target_dir}/zed.dwarf"
+    else
+        echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
+    fi
+}
+
+if command -v sentry-cli >/dev/null 2>&1; then
+    upload_debug_info "aarch64-apple-darwin"
+    upload_debug_info "x86_64-apple-darwin"
+else
+    echo "sentry-cli not found. skipping sentry upload."
+    echo "install with: 'curl -sL https://sentry.io/get-cli | bash'"
+fi

script/bundle-mac 🔗

@@ -7,14 +7,9 @@ build_flag="--release"
 target_dir="release"
 open_result=false
 local_arch=false
-local_only=false
 local_install=false
 bundle_name=""
-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() {
@@ -31,21 +26,26 @@ Options:
   "
 }
 
+build_args=""
 while getopts 'dloih' flag
 do
     case "${flag}" in
-        o) open_result=true;;
+        o) open_result=true
+           build_args="${build_args} -o"
+           ;;
         d)
             export CARGO_INCREMENTAL=true
             export CARGO_BUNDLE_SKIP_BUILD=true
             build_flag="";
             target_dir="debug"
+            build_args="${build_args} -d"
             ;;
         l)
             export CARGO_INCREMENTAL=true
             export CARGO_BUNDLE_SKIP_BUILD=true
             local_arch=true
             local_only=true
+            build_args="${build_args} -l"
             ;;
         i) local_install=true;;
         h)
@@ -63,307 +63,9 @@ if [[ $# -gt 0 ]]; then
     fi
 fi
 
-# 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)
-local_target_triple=${host_line#*: }
-
-# Generate the licenses first, so they can be baked into the binaries
-script/generate-licenses
-
-if [ "$local_arch" = true ]; then
-    echo "Building for local target only."
-    cargo build ${build_flag} --package zed --package cli --package remote_server
-else
-    rustup target add aarch64-apple-darwin
-    rustup target add x86_64-apple-darwin
-
-    echo "Compiling zed binaries"
-    cargo build ${build_flag} --package zed --package cli --target aarch64-apple-darwin --target x86_64-apple-darwin
-    # 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 aarch64-apple-darwin --target x86_64-apple-darwin
-fi
-
-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
-
-if [ "$local_arch" = true ]; then
-    app_path=$(cargo bundle ${build_flag} --select-workspace-root | xargs)
-else
-    app_path_x64=$(cargo bundle ${build_flag} --target x86_64-apple-darwin --select-workspace-root | xargs)
-    app_path_aarch64=$(cargo bundle ${build_flag} --target aarch64-apple-darwin --select-workspace-root | xargs)
-    app_path=$app_path_x64
-fi
-
-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 prepare_binaries() {
-    local architecture=$1
-    local app_path=$2
-
-    cp target/${architecture}/${target_dir}/zed "${app_path}/Contents/MacOS/zed"
-    cp target/${architecture}/${target_dir}/cli "${app_path}/Contents/MacOS/cli"
-}
-
-function sign_app_binaries() {
-    local app_path=$1
-    local architecture=$2
-    local architecture_dir=$3
-    rm -rf "${app_path}/Contents/Frameworks"
-    mkdir -p "${app_path}/Contents/Frameworks"
-    if [ "$local_arch" = true ]; then
-        cp -R target/${target_dir}/cli "${app_path}/Contents/MacOS/"
-    fi
-
-    echo "Downloading git binary"
-    download_git "${architecture}" "${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"
-        if [[ "$local_only" = false ]]; then
-            echo "To create a self-signed local build use ./scripts/build.sh -ldf"
-            exit 1
-        fi
-
-        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" && "$local_only" = false ]]; then
-        if [ "$open_result" = true ]; then
-            open "$app_path"
-        else
-            echo "Created application bundle:"
-            echo "$app_path"
-        fi
-        exit 0
-    fi
-
-    # If bundle_name is not set or empty, use the basename of $app_path
-    if [ -z "$bundle_name" ]; then
-        bundle_name=$(basename "$app_path")
-    else
-        # If bundle_name doesn't end in .app, append it
-        if [[ "$bundle_name" != *.app ]]; then
-            bundle_name="$bundle_name.app"
-        fi
-    fi
-
-    if [ "$local_only" = true ]; then
-        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
-            if [ "$open_result" = true ]; then
-                echo "Opening $app_path"
-                open "$app_path"
-            fi
-        fi
-    else
-        dmg_target_directory="target/${architecture_dir}/${target_dir}"
-        dmg_source_directory="${dmg_target_directory}/dmg"
-        dmg_file_path="${dmg_target_directory}/Zed.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
-}
-
-if [ "$local_arch" = true ]; then
-    sign_app_binaries "$app_path" "$local_target_triple" "$local_target_triple"
-
-    sign_binary "target/release/remote_server"
-else
-    # Create universal binary
-    prepare_binaries "aarch64-apple-darwin" "$app_path_aarch64"
-    prepare_binaries "x86_64-apple-darwin" "$app_path_x64"
-
-
-    sign_app_binaries "$app_path_x64" "x86_64-apple-darwin" "x86_64-apple-darwin"
-    sign_app_binaries "$app_path_aarch64" "aarch64-apple-darwin" "aarch64-apple-darwin"
-
-    sign_binary "target/x86_64-apple-darwin/release/remote_server"
-    sign_binary "target/aarch64-apple-darwin/release/remote_server"
-    gzip -f --stdout --best target/x86_64-apple-darwin/release/remote_server > target/zed-remote-server-macos-x86_64.gz
-    gzip -f --stdout --best target/aarch64-apple-darwin/release/remote_server > target/zed-remote-server-macos-aarch64.gz
-fi
-
-function upload_debug_info() {
-    architecture=$1
-    if [[ -n "${SENTRY_AUTH_TOKEN:-}" ]]; then
-        echo "Uploading zed debug symbols to sentry..."
-        # 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/${architecture}/${target_dir}/zed" \
-            "target/${architecture}/${target_dir}/remote_server" \
-            "target/${architecture}/${target_dir}/zed.dwarf"
-    else
-        echo "missing SENTRY_AUTH_TOKEN. skipping sentry upload."
-    fi
-}
-
-if command -v sentry-cli >/dev/null 2>&1; then
-    upload_debug_info "aarch64-apple-darwin"
-    upload_debug_info "x86_64-apple-darwin"
+script/build-mac $build_args
+if [ "$local_only" = true ]; then
+    script/notarize-mac $bundle_name
 else
-    echo "sentry-cli not found. skipping sentry upload."
-    echo "install with: 'curl -sL https://sentry.io/get-cli | bash'"
+    script/notarize-mac $bundle_name
 fi

script/notarize-mac 🔗

@@ -0,0 +1,255 @@
+# This must match the team in the provisioning profile.
+IDENTITY="Zed Industries, Inc."
+APPLE_NOTARIZATION_TEAM="MQ55VZLNZQ"
+
+local_arch=false
+local_only=false
+local_install=false
+can_code_sign=false
+app_path=""
+app_target=""
+# Function for displaying help info
+help_info() {
+  echo "
+Usage: ${0##*/} [options] [bundle_name]
+Notarize the application bundle for macOS.
+
+Options:
+  -l    Notarize local architecture only.
+  -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 'loih' flag
+do
+    case "${flag}" in
+        o) open_result=true
+           ;;
+        l)
+            local_arch=true
+            local_only=true
+            ;;
+        i) local_install=true;;
+        h)
+           help_info
+           exit 0
+           ;;
+    esac
+done
+
+shift
+if [[ $# -gt 0 ]]; then
+    if [ "$1" ]; then
+        app_path=$1
+    fi
+    if [ "$2" ]; then
+        app_target=$2
+    fi
+fi
+
+pushd crates/zed
+channel=$(<RELEASE_CHANNEL)
+export ZED_RELEASE_CHANNEL="${channel}"
+popd
+
+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() {
+    local app_path=$1
+    local architecture=$2
+    local architecture_dir=$architecture
+    rm -rf "${app_path}/Contents/Frameworks"
+    mkdir -p "${app_path}/Contents/Frameworks"
+
+    echo "Downloading git binary"
+    download_git "${architecture}" "${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"
+        if [[ "$local_only" = false ]]; then
+            echo "To create a self-signed local build use ./scripts/build.sh -ldf"
+            exit 1
+        fi
+
+        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" && "$local_only" = false ]]; 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 bundle_name doesn't end in .app, append it
+    if [[ "$bundle_name" != *.app ]]; then
+        bundle_name="$bundle_name.app"
+    fi
+
+    if [ "$local_only" = true ]; then
+        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
+            if [ "$open_result" = true ]; then
+                echo "Opening $app_path"
+                open "$app_path"
+            fi
+        fi
+    else
+        dmg_target_directory="target/${architecture_dir}/${target_dir}"
+        dmg_source_directory="${dmg_target_directory}/dmg"
+        dmg_file_path="${dmg_target_directory}/Zed.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
+}
+
+sign_app_binaries  "$app_path" "$app_target"
+sign_binary "target/x86_64-apple-darwin/release/remote_server"
+
+if [ "$local_arch" = false ]; then
+    gzip -f --stdout --best target/x86_64-apple-darwin/release/remote_server > target/zed-remote-server-macos-x86_64.gz
+fi