#!/usr/bin/env bash # First-batch bundles open Finder's files on their own engines. set -euo pipefail root=$(cd "$(dirname "$0")/../.." && pwd) [ "$(uname)" = Darwin ] || { echo "SKIP release-macos: macOS only" exit 0 } # Real path: the apps log theirs. dist=$(cd "${ELY_DIST:-$root/dist}" && pwd -P) work=$(mktemp -d) home=$work/home trap 'pkill -f "$dist/Ely[A-Za-z]*.app/Contents/MacOS/" 2>/dev/null || true; sleep 1; rm -rf "$work"' EXIT fail() { echo "FAIL: $*" >&2 exit 1 } apps=$(awk -F'\t' '$4 == 1 { print $1 "\t" $2 }' "$root/packaging/apps.tsv") [ "$(echo "$apps" | wc -l | tr -d ' ')" = 6 ] || fail "the first batch is not six apps" while IFS=$'\t' read -r slug name; do [ -d "$dist/$name.app" ] || fail "no $dist/$name.app; run packaging/macos/release.sh" done <<<"$apps" version=$(awk -F'"' '/^version = / { print $2; exit }' "$root/Cargo.toml") commit=$(git -C "$root" rev-parse --short=12 HEAD) source=$dist/ElyOffice-$version-$commit-source.tar.gz [ -s "$source" ] || fail "no $source; run packaging/macos/release.sh" listed=$(tar -tzf "$source") for part in LICENSE Cargo.lock patches/libreoffice/ patches/mupdf/ scripts/fetch-references.sh scripts/build-git.sh; do grep -qx "ElyOffice-$version/$part" <<<"$listed" || fail "the source archive lacks $part" done for part in agents.md PRD.md tasks/; do ! grep -qx "ElyOffice-$version/$part" <<<"$listed" || fail "the source archive has the internal $part" done # Copyleft upstream as built: MuPDF with our patches, and Git. upstream=$dist/ElyOffice-$version-$commit-upstream-source.tar.gz [ -s "$upstream" ] || fail "no $upstream; run packaging/macos/release.sh" for part in mupdf-src/COPYING git/COPYING git/GIT-VERSION-GEN; do tar -tzf "$upstream" "$part" >/dev/null 2>&1 || fail "the upstream archive lacks $part" done tar -xzOf "$upstream" mupdf-src/include/mupdf/pdf/form.h | grep -q pdf_signature_set_value_said || fail "the MuPDF source is not the patched one" # Samples: fixtures, so no LibreOffice app ever runs here. files=$work/files mkdir -p "$files/repo" cp "$root"/tests/fixtures/office/t.* "$files/" printf '# Bundle\n' >"$files/t.md" for kind in docx xlsx pptx pdf md; do [ -s "$files/t.$kind" ] || fail "no t.$kind sample" cp "$files/t.$kind" "$files/u.$kind" done git -C "$files/repo" init -q mkdir -p "$files/second" && git -C "$files/second" init -q log_of() { echo "$home/Library/Application Support/ElyOffice/$1/ely.log"; } waits() { # slug, text for _ in $(seq 240); do grep -qF "$2" "$(log_of "$1")" 2>/dev/null && return 0 sleep 0.25 done fail "$1 never logged '$2': $(tail -n 5 "$(log_of "$1")" 2>/dev/null)" } while IFS=$'\t' read -r slug name; do app=$dist/$name.app codesign --verify --deep --strict "$app" || fail "$name's signature does not verify" icon=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIconFile" "$app/Contents/Info.plist") [ -s "$app/Contents/Resources/$icon.icns" ] || fail "$name has no icon" # Licences travel with the app (§21.3). licenses=$app/Contents/Resources/Licenses case $slug in ely-doc | ely-table | ely-show) engines=(LibreOffice) ;; ely-pdf) engines=(MuPDF MuPDF/fonts/urw MuPDF/fonts/sil MuPDF/fonts/noto MuPDF/fonts/droid MuPDF/fonts/han OpenSSL tessdata GreatVibes) ;; ely-tree) engines=(Git git-lfs) ;; ely-mark) engines=(KaTeX MuPDF MuPDF/fonts/noto OpenSSL GreatVibes) ;; esac for part in LICENSE rust-crates.txt "${engines[@]}"; do [ -n "$(find "$licenses/$part" -type f -size +0 2>/dev/null)" ] || fail "$name lacks the licences of $part" done # Each font family needs a licence, not just a README. for family in "$licenses"/MuPDF/fonts/*; do [ -d "$family" ] || continue [ -n "$(find "$family" -type f -size +0 \( -iname 'licen[cs]e*' -o -iname 'copying*' -o -iname 'notice*' -o -iname 'ofl*' \))" ] || fail "$name lacks the licence of the ${family##*/} fonts" done # Nothing inside registers with macOS. extra=$(find "$app/Contents" \( -name '*.app' -o -name '*.appex' -o -name '*.mdimporter' -o -name '*.qlgenerator' \)) [ -z "$extra" ] || fail "$name carries bundles macOS registers: $extra" # No binary may need a newer macOS than the app. least=$(/usr/libexec/PlistBuddy -c "Print :LSMinimumSystemVersion" "$app/Contents/Info.plist") while IFS= read -r binary; do file -b "$binary" | grep -q '^Mach-O' || continue # vtool: otool reads "name (x)" as an archive member. minos=$(vtool -show-build "$binary" | awk '/minos/ && !v { v = $2 } END { print v }') [ -z "$minos" ] || [ "$(printf '%s\n%s\n' "$minos" "$least" | sort -V | tail -n 1)" = "$least" ] || fail "${binary#"$app"/} needs macOS $minos; $name says $least" done < <(find "$app" -type f \( -perm -111 -o -name '*.dylib' \)) case $slug in ely-doc) first=t.docx second=u.docx ;; ely-table) first=t.xlsx second=u.xlsx ;; ely-show) first=t.pptx second=u.pptx ;; ely-pdf) first=t.pdf second=u.pdf ;; ely-mark) first=t.md second=u.md ;; ely-tree) first=repo second=second ;; esac # Finder's launch: no arguments, an open request. open -n -a "$app" --env "HOME=$home" --env ELY_LOG=info --env ELY_NO_DIALOG=1 "$files/$first" case $slug in ely-tree) opened() { echo "tree: opened $(cd "$files/$1" && pwd -P)"; } ;; *) opened() { echo "$slug: opened $1 as tab"; } ;; esac waits "$slug" "$(opened "$first")" log=$(log_of "$slug") case $slug in ely-doc | ely-table | ely-show) grep -qF "lok: worker from $app/Contents/MacOS/ely-lok-worker" "$log" || fail "$name used another LOK worker" grep -qF "worker from $app/Contents/MacOS/../Frameworks" "$log" || fail "$name used another kernel: $(grep 'worker from' "$log")" ! grep -q "opened Untitled" "$log" || fail "$name opened a blank document beside the file" ;; ely-pdf) grep -qF "pdf: worker from $app/Contents/MacOS/ely-pdf-worker" "$log" || fail "ElyPDF used another worker" ;; ely-tree) grep -qF "at $app/Contents/MacOS/../Resources/git/bin/git (bundled)" "$log" || fail "ElyTree used another Git" # Built-ins stay links to one binary, not copies. [ "$(du -sm "$app/Contents/Resources/git" | cut -f1)" -lt 100 ] || fail "ElyTree's Git copies its built-ins" ;; esac # A running app takes Finder's next file. open -a "$app" "$files/$second" waits "$slug" "$(opened "$second")" pkill -f "$app/Contents/MacOS/$slug" || true echo "ok: $name" done <<<"$apps" # The app menu starts another installed app (§16). opens=$(grep -c "ElyDoc window open" "$(log_of ely-doc)") open -n -a "$dist/ElyDoc.app" --env "HOME=$home" --env ELY_LOCALE=en --env ELY_NO_DIALOG=1 for _ in $(seq 120); do [ "$(grep -c "ElyDoc window open" "$(log_of ely-doc)")" -gt "$opens" ] && break; sleep 0.25; done doc=$(pgrep -f "$dist/ElyDoc.app/Contents/MacOS/ely-doc" | head -n 1) [ -n "$doc" ] || fail "ElyDoc did not start" osascript -e "tell application \"System Events\" to tell (first process whose unix id is $doc)" \ -e 'set frontmost to true' -e 'delay 1' \ -e 'click menu item "ElyMark" of menu 1 of menu item "Open Ely App" of menu 1 of menu bar item 2 of menu bar 1' \ -e 'end tell' >/dev/null || fail "ElyDoc's app menu has no ElyMark" for _ in $(seq 40); do pgrep -f "$dist/ElyMark.app/Contents/MacOS/ely-mark" >/dev/null && break; sleep 0.25; done pgrep -f "$dist/ElyMark.app/Contents/MacOS/ely-mark" >/dev/null || fail "the menu did not start ElyMark" pkill -f "$dist/Ely[A-Za-z]*.app/Contents/MacOS/" || true echo "release-macos: 6 bundles open files on their own engines and start each other"