Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
817 files changed
+143101
No files matched your search
Executable
+170
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds dist/<Name>.app. Usage: bundle.sh <slug> [release|debug]
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
slug=${1:?app slug, such as ely-doc}
|
||||
profile=${2:-release}
|
||||
dist=${ELY_DIST:-$root/dist}
|
||||
identity=${ELY_SIGN_IDENTITY:--}
|
||||
prefix=ely.zacharyzhang.office
|
||||
|
||||
name=$(awk -F'\t' -v slug="$slug" '$1 == slug { print $2 }' "$root/packaging/apps.tsv")
|
||||
[ -n "$name" ] || {
|
||||
echo "bundle.sh: unknown app $slug" >&2
|
||||
exit 1
|
||||
}
|
||||
version=$(awk -F'"' '/^version = / { print $2; exit }' "$root/Cargo.toml")
|
||||
case $profile in
|
||||
release | debug) ;;
|
||||
*)
|
||||
echo "bundle.sh: profile is release or debug" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
build() { # cargo build arguments
|
||||
case $profile in
|
||||
release) cargo build --manifest-path "$root/Cargo.toml" --release "$@" ;;
|
||||
debug) cargo build --manifest-path "$root/Cargo.toml" "$@" ;;
|
||||
esac
|
||||
}
|
||||
uses() { grep -q "^$1" "$root/apps/$slug/Cargo.toml"; }
|
||||
need() { # path, what to run
|
||||
[ -e "$1" ] || {
|
||||
echo "bundle.sh: no $1; run $2" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
build -p "$slug"
|
||||
icon=$root/assets/icons/$slug.icns
|
||||
need "$icon" packaging/icons/build.sh
|
||||
|
||||
app=$dist/$name.app
|
||||
rm -rf "$app"
|
||||
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
|
||||
cp "$root/target/$profile/$slug" "$app/Contents/MacOS/$slug"
|
||||
cp "$icon" "$app/Contents/Resources/$slug.icns"
|
||||
# Code inside the app, signed before it.
|
||||
inner=()
|
||||
if uses ely-lok; then
|
||||
lo=${ELY_LO_APP:-$root/engines/lo-src/instdir/LibreOffice.app}
|
||||
need "$lo/Contents/Frameworks" scripts/build-libreoffice.sh
|
||||
build -p ely-lok --bin ely-lok-worker
|
||||
cp "$root/target/$profile/ely-lok-worker" "$app/Contents/MacOS/"
|
||||
# The kernel alone, in LibreOffice's layout: nothing to register.
|
||||
mkdir -p "$app/Contents/Frameworks"
|
||||
ditto "$lo/Contents/Frameworks" "$app/Contents/Frameworks"
|
||||
ditto "$lo/Contents/Resources" "$app/Contents/Resources"
|
||||
inner+=("$app/Contents/MacOS/ely-lok-worker")
|
||||
while IFS= read -r binary; do
|
||||
if file -b "$binary" | grep -q '^Mach-O'; then
|
||||
inner+=("$binary")
|
||||
elif [ "$(cat "$binary")" = "invalid - merged lib" ]; then
|
||||
# Merged-library builds leave these placeholders.
|
||||
rm "$binary"
|
||||
else
|
||||
echo "bundle.sh: $binary is not code; Frameworks holds only code" >&2
|
||||
exit 1
|
||||
fi
|
||||
done < <(find "$app/Contents/Frameworks" -type f -not -path "*/Chromium Embedded Framework.framework/*")
|
||||
fi
|
||||
if uses ely-pdf-engine; then
|
||||
tessdata=${ELY_TESSDATA:-$root/engines/tessdata}
|
||||
need "$tessdata/eng.traineddata" scripts/fetch-tessdata.sh
|
||||
build -p ely-pdf-engine --bin ely-pdf-worker
|
||||
cp "$root/target/$profile/ely-pdf-worker" "$app/Contents/MacOS/"
|
||||
mkdir "$app/Contents/Resources/tessdata"
|
||||
cp "$tessdata"/{eng,chi_sim,chi_tra}.traineddata "$app/Contents/Resources/tessdata/"
|
||||
inner+=("$app/Contents/MacOS/ely-pdf-worker")
|
||||
fi
|
||||
if [ "$slug" = ely-tree ]; then
|
||||
need "$root/engines/git/bin/git" "scripts/build-git.sh and scripts/fetch-git-lfs.sh"
|
||||
ditto "$root/engines/git" "$app/Contents/Resources/git"
|
||||
while IFS= read -r binary; do
|
||||
file -b "$binary" | grep -q '^Mach-O' && inner+=("$binary")
|
||||
done < <(find "$app/Contents/Resources/git" -type f -perm -111)
|
||||
fi
|
||||
# Web content apps carry CEF and their bundled pages.
|
||||
framework=
|
||||
helpers=()
|
||||
if grep -q '^ely-web-content' "$root/apps/$slug/Cargo.toml"; then
|
||||
cef=${ELY_CEF:-$root/engines/cef}
|
||||
[ -d "$cef/Chromium Embedded Framework.framework" ] || {
|
||||
echo "bundle.sh: no CEF at $cef; run scripts/fetch-cef.sh" >&2
|
||||
exit 1
|
||||
}
|
||||
mkdir -p "$app/Contents/Frameworks"
|
||||
framework="$app/Contents/Frameworks/Chromium Embedded Framework.framework"
|
||||
cp -R "$cef/Chromium Embedded Framework.framework" "$framework"
|
||||
case $profile in
|
||||
release) cargo build --manifest-path "$root/Cargo.toml" --release -p ely-web-content --bin ely-web-helper ;;
|
||||
debug) cargo build --manifest-path "$root/Cargo.toml" -p ely-web-content --bin ely-web-helper ;;
|
||||
esac
|
||||
# Chromium looks for these five, named after the app's binary.
|
||||
for kind in "" " (GPU)" " (Renderer)" " (Plugin)" " (Alerts)"; do
|
||||
helper="$slug Helper$kind"
|
||||
contents="$app/Contents/Frameworks/$helper.app/Contents"
|
||||
mkdir -p "$contents/MacOS"
|
||||
cp "$root/target/$profile/ely-web-helper" "$contents/MacOS/$helper"
|
||||
id=$(echo "${slug#ely-}.helper$kind" | tr -d '()' | tr ' ' '.' | tr 'A-Z' 'a-z')
|
||||
cat >"$contents/Info.plist" <<HELPER
|
||||
<?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>CFBundleExecutable</key><string>$helper</string>
|
||||
<key>CFBundleIdentifier</key><string>$prefix.$id</string>
|
||||
<key>CFBundleName</key><string>$helper</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key><string>$version</string>
|
||||
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
||||
<key>LSUIElement</key><string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
HELPER
|
||||
helpers+=("$app/Contents/Frameworks/$helper.app")
|
||||
done
|
||||
content=$root/engines/content/$slug
|
||||
[ -f "$content/index.html" ] || {
|
||||
echo "bundle.sh: no page at $content; run its scripts/build-*-content.sh" >&2
|
||||
exit 1
|
||||
}
|
||||
cp -R "$content" "$app/Contents/Resources/content"
|
||||
fi
|
||||
"$root/packaging/licenses.sh" "$slug" "$app/Contents/Resources/Licenses"
|
||||
cat >"$app/Contents/Info.plist" <<PLIST
|
||||
<?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>CFBundleExecutable</key><string>$slug</string>
|
||||
<key>CFBundleIconFile</key><string>$slug</string>
|
||||
<key>CFBundleIdentifier</key><string>$prefix.${slug#ely-}</string>
|
||||
<key>CFBundleName</key><string>$name</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key><string>$version</string>
|
||||
<key>CFBundleVersion</key><string>$version</string>
|
||||
<key>LSApplicationCategoryType</key><string>public.app-category.productivity</string>
|
||||
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
||||
<key>NSHighResolutionCapable</key><true/>
|
||||
<key>NSHumanReadableCopyright</key><string>ZacharyZhang, zacharyzhang.com</string>
|
||||
$("$root/packaging/macos/documents.sh" "$slug")
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
# Nested code is signed before the app that holds it.
|
||||
for target in ${inner[@]+"${inner[@]}"} ${framework:+"$framework"} ${helpers[@]+"${helpers[@]}"} "$app"; do
|
||||
if [ "$identity" = - ]; then
|
||||
codesign --force --sign - "$target"
|
||||
else
|
||||
# Hardened runtime: V8 in CEF's helpers needs its entitlements.
|
||||
entitled=()
|
||||
case $target in
|
||||
*" Helper (Renderer).app") entitled=(--entitlements "$root/packaging/macos/entitlements/renderer.plist") ;;
|
||||
*" Helper (GPU).app") entitled=(--entitlements "$root/packaging/macos/entitlements/gpu.plist") ;;
|
||||
*" Helper (Plugin).app") entitled=(--entitlements "$root/packaging/macos/entitlements/plugin.plist") ;;
|
||||
esac
|
||||
codesign --force --options runtime --timestamp ${entitled[@]+"${entitled[@]}"} --sign "$identity" "$target"
|
||||
fi
|
||||
done
|
||||
echo "$app"
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prints the app's CFBundleDocumentTypes. Usage: documents.sh <slug>
|
||||
set -euo pipefail
|
||||
|
||||
# role, then the content types it opens.
|
||||
case ${1:?app slug} in
|
||||
ely-doc) set -- Editor org.openxmlformats.wordprocessingml.document org.oasis-open.opendocument.text \
|
||||
com.microsoft.word.doc public.rtf ;;
|
||||
ely-table) set -- Editor org.openxmlformats.spreadsheetml.sheet org.oasis-open.opendocument.spreadsheet \
|
||||
com.microsoft.excel.xls public.comma-separated-values-text public.tab-separated-values-text ;;
|
||||
ely-show) set -- Editor org.openxmlformats.presentationml.presentation \
|
||||
org.oasis-open.opendocument.presentation com.microsoft.powerpoint.ppt ;;
|
||||
ely-pdf) set -- Editor com.adobe.pdf ;;
|
||||
ely-mark) set -- Editor net.daringfireball.markdown ;;
|
||||
ely-tree) set -- Viewer public.folder ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
role=$1
|
||||
shift
|
||||
echo " <key>CFBundleDocumentTypes</key><array><dict>"
|
||||
echo " <key>CFBundleTypeRole</key><string>$role</string>"
|
||||
echo " <key>LSHandlerRank</key><string>Alternate</string>"
|
||||
echo " <key>LSItemContentTypes</key><array>"
|
||||
for type in "$@"; do echo " <string>$type</string>"; done
|
||||
echo " </array>"
|
||||
echo " </dict></array>"
|
||||
@@ -0,0 +1,7 @@
|
||||
<?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.allow-jit</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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.allow-unsigned-executable-memory</key><true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?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.allow-jit</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: release.sh [batch]; a dmg per app of the batch.
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
batch=${1:-1}
|
||||
dist=${ELY_DIST:-$root/dist}
|
||||
version=$(awk -F'"' '/^version = / { print $2; exit }' "$root/Cargo.toml")
|
||||
arch=$(uname -m)
|
||||
[ -z "${ELY_NOTARY_PROFILE:-}" ] || [ -n "${ELY_SIGN_IDENTITY:-}" ] || {
|
||||
echo "release.sh: notarizing needs ELY_SIGN_IDENTITY, a Developer ID" >&2
|
||||
exit 1
|
||||
}
|
||||
# Our source as published: the committed tree.
|
||||
source=$dist/ElyOffice-$version-source.tar.gz
|
||||
mkdir -p "$dist"
|
||||
git -C "$root" archive --format=tar.gz --prefix="ElyOffice-$version/" -o "$source" HEAD
|
||||
echo "$source"
|
||||
# Copyleft upstream as shipped: patched MuPDF (AGPL), Git (GPLv2).
|
||||
upstream=$dist/ElyOffice-$version-upstream-source.tar.gz
|
||||
[ -f "$root/engines/mupdf-src/COPYING" ] || { echo "release.sh: no engines/mupdf-src; run the MuPDF build" >&2; exit 1; }
|
||||
tar -czf "$upstream" --exclude .git --exclude .DS_Store \
|
||||
-C "$root/engines" mupdf-src -C "$root/reference" git
|
||||
echo "$upstream"
|
||||
awk -F'\t' -v batch="$batch" '$4 == batch { print $1 "\t" $2 }' "$root/packaging/apps.tsv" |
|
||||
while IFS=$'\t' read -r slug name; do
|
||||
"$root/packaging/macos/bundle.sh" "$slug" release
|
||||
codesign --verify --deep --strict "$dist/$name.app"
|
||||
dmg=$dist/$name-$version-$arch.dmg
|
||||
rm -f "$dmg"
|
||||
hdiutil create -quiet -volname "$name" -srcfolder "$dist/$name.app" -format UDZO "$dmg"
|
||||
if [ -n "${ELY_NOTARY_PROFILE:-}" ]; then
|
||||
codesign --force --timestamp --sign "$ELY_SIGN_IDENTITY" "$dmg"
|
||||
xcrun notarytool submit "$dmg" --keychain-profile "$ELY_NOTARY_PROFILE" --wait
|
||||
xcrun stapler staple "$dmg"
|
||||
else
|
||||
echo "release.sh: $dmg is not notarized (no ELY_NOTARY_PROFILE)" >&2
|
||||
fi
|
||||
echo "$dmg"
|
||||
done
|
||||
rm -rf "$root/target/release/incremental"
|
||||
Reference in new issue
Block a user