Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:

- name: Build release binary
env:
MACOSX_DEPLOYMENT_TARGET: ${{ runner.os == 'macOS' && '12.0' || '' }}
MACOSX_DEPLOYMENT_TARGET: ${{ runner.os == 'macOS' && '13.0' || '' }}
run: cargo build --release --locked --target "${{ matrix.target }}" --bin shosai

- name: Download pinned PDFium
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ test:
test-scripts:
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \
-s benchmarks/epub-page-turn/2026-08-17/tests
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \
-s scripts/tests

## Regenerate CHANGELOG.md from conventional commits
changelog:
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/001-epub-renderer.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ Those behaviors need explicit tests rather than visual inference.

| Target | Child-view path | Current finding |
|---------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| macOS 12+ | Iced raw AppKit handle → child `WKWebView` | Builds and renders with the system WebKit; no extra runtime packaged |
| macOS 13+ | Iced raw AppKit handle → child `WKWebView` | Builds and renders with the system WebKit; no extra runtime packaged |
| Windows CI | Iced raw Win32 handle → child WebView2 | API path exists; runtime, accessibility, focus, and CI spike not yet tested |
| Linux X11 | Iced raw Xlib handle → WebKitGTK child | Builds on x86_64; automated resize/teardown and zero-network proofs pass under Xvfb; interactive and arm64 evidence remain open |
| Linux Wayland | GTK container via Wry Unix extension | Raw child embedding is unsupported; standard Iced runner exposes no GTK container, making this the main Wry portability blocker |
Expand Down
14 changes: 11 additions & 3 deletions docs/plans/001-enhanced-epub-rendering.org
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,17 @@ Working evidence: [[file:../adr/001-epub-renderer.md][ADR 001: EPUB renderer]].
~app/epub_navigation.rs~. Image presentation caching plus paginated and
continuous EPUB widget construction now live in ~app/epub_view.rs~; generic
reader chrome and state/update ownership remain in ~app.rs~.
- [ ] Add feature/build configuration and packaging changes only for the native
implementation; document runtime prerequisites and attribution.
- [ ] Run the full workspace test suite and platform packaging smoke tests.
- [X] Add feature/build configuration and packaging changes only for the native
implementation; document runtime prerequisites and attribution. Release
packages bundle PDFium and Inter with their licenses. macOS release builds
use the host Apple compiler and SDK with a macOS 13 deployment target, and
package validation rejects newer or non-portable Mach-O load commands.
- [X] Run the full workspace test suite and platform packaging smoke tests.
The final M5 stack passes 421 Rust tests and 10 script tests on native
x86_64 Linux, plus release build, package validation, and extracted-app
runtime smoke tests on Linux and Apple Silicon macOS. macOS inspection
confirms the app and bundled PDFium both target macOS 13 and contain no
non-system absolute load dependencies.

* Tests and Fixtures

Expand Down
2 changes: 1 addition & 1 deletion docs/plans/005-distribution-packaging.org
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ Verify all downloaded installer tools and PDFium inputs by digest.
- Apple Developer Program membership, a Developer ID Application certificate,
and an App Store Connect issuer/key ID/API key (or approved notarytool profile).
- Confirm hardened-runtime entitlements by testing both architectures, PDFium,
file dialogs, and GPU rendering. Keep ~LSMinimumSystemVersion=12.0~ unless
file dialogs, and GPU rendering. Keep ~LSMinimumSystemVersion=13.0~ unless
compatibility testing changes it.

** Actionable changes and likely files
Expand Down
4 changes: 3 additions & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Linux packages contain an optimized binary, PDFium, licenses, a desktop entry, a

The macOS zip contains `Shosai.app`. Extract it and move the application into `/Applications` or `~/Applications`. The bundle is ad-hoc signed but is not yet Developer ID signed or notarized, so macOS may show a Gatekeeper warning for downloaded releases.

PDFium is pinned to the checksummed `chromium/7999` binaries from `bblanchon/pdfium-binaries`. Shosai resolves the bundled library relative to its executable and falls back to the system library for development builds.
macOS release builds require the Apple Command Line Tools and target macOS 13, matching the bundled PDFium binary. The Nix development shell selects the host Apple compiler and SDK for Cargo release builds so packaged binaries link only to system or bundle-relative libraries. Package validation rejects Mach-O files whose deployment target exceeds the bundle's declared minimum or whose load commands contain non-portable absolute dependencies.

PDFium is pinned to the checksummed `chromium/7999` binaries from `bblanchon/pdfium-binaries`. Shosai resolves the bundled library relative to its executable and falls back to the system library for development builds. Release packages include the project, PDFium, and Inter font licenses alongside the corresponding runtime assets.

## Required repository settings

Expand Down
11 changes: 11 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,17 @@
// (pkgs.lib.optionalAttrs pkgs.stdenv.isDarwin {
# macOS: DYLD_LIBRARY_PATH for dynamic libraries
DYLD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.pdfium-binaries ];

# Release artifacts must use the host Apple SDK and system libraries,
# rather than embedding dependencies from the Nix SDK or store.
CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER = "/usr/bin/clang";
CC_aarch64_apple_darwin = "/usr/bin/clang";
CXX_aarch64_apple_darwin = "/usr/bin/clang++";
shellHook = ''
export DEVELOPER_DIR=/Library/Developer/CommandLineTools
export SDKROOT="$DEVELOPER_DIR/SDKs/MacOSX.sdk"
export MACOSX_DEPLOYMENT_TARGET=13.0
'';
})
);
}
Expand Down
130 changes: 124 additions & 6 deletions scripts/check-macos-package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fi
version=${1#v}
architecture=$2
archive=$3
plist_buddy=${PLIST_BUDDY:-/usr/libexec/PlistBuddy}

case "$architecture" in
x86_64) binary_architecture="x86_64" ;;
Expand Down Expand Up @@ -59,14 +60,131 @@ test "$(lipo -archs "$binary")" = "$binary_architecture"
test "$(lipo -archs "$pdfium")" = "$binary_architecture"

plutil -lint "$info_plist"
test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$info_plist")" = \
test "$("$plist_buddy" -c 'Print :CFBundleIdentifier' "$info_plist")" = \
"io.github.chaba2.shosai"
test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$info_plist")" = "Shosai"
test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$info_plist")" = "Shosai"
test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$info_plist")" = \
test "$("$plist_buddy" -c 'Print :CFBundleExecutable' "$info_plist")" = "Shosai"
test "$("$plist_buddy" -c 'Print :CFBundleIconFile' "$info_plist")" = "Shosai"
test "$("$plist_buddy" -c 'Print :CFBundleShortVersionString' "$info_plist")" = \
"$version"
test "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$info_plist")" = "$version"
test "$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$info_plist")" = "12.0"
test "$("$plist_buddy" -c 'Print :CFBundleVersion' "$info_plist")" = "$version"
minimum_system_version=$("$plist_buddy" -c 'Print :LSMinimumSystemVersion' "$info_plist")
test "$minimum_system_version" = "13.0"

macho_build_target() {
awk '
$1 == "cmd" { command = $2; platform = ""; next }
command == "LC_BUILD_VERSION" && $1 == "platform" { platform = $2; next }
command == "LC_BUILD_VERSION" && $1 == "minos" { print platform, $2; exit }
command == "LC_VERSION_MIN_MACOSX" && $1 == "version" { print "MACOS", $2; exit }
'
}

version_is_greater() {
awk -v actual="$1" -v declared="$2" 'BEGIN {
split(actual, left, "."); split(declared, right, ".")
for (part = 1; part <= 4; part++) {
if ((left[part] + 0) > (right[part] + 0)) exit 0
if ((left[part] + 0) < (right[part] + 0)) exit 1
}
exit 1
}'
}

path_has_dot_component() {
case "/$1/" in
*/./*|*/../*) return 0 ;;
*) return 1 ;;
esac
}

check_macho_portability() {
local label=$1
local macho=$2
local load_commands
if ! load_commands=$(otool -l "$macho"); then
echo "cannot inspect Mach-O load commands for $label" >&2
exit 1
fi

local target
local platform
local minimum
target=$(printf '%s\n' "$load_commands" | macho_build_target)
read -r platform minimum <<< "$target"
if [[ -z "$minimum" ]]; then
echo "cannot determine minimum macOS version for $label" >&2
exit 1
fi
case "$platform" in
1|MACOS) ;;
*)
echo "$label is not a macOS Mach-O (platform $platform)" >&2
exit 1
;;
esac
if version_is_greater "$minimum" "$minimum_system_version"; then
echo "$label requires macOS $minimum but package declares $minimum_system_version" >&2
exit 1
fi

local rpaths
rpaths=$(printf '%s\n' "$load_commands" | awk '
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
in_rpath && $1 == "path" {
line = $0
sub(/^[[:space:]]*path[[:space:]]+/, "", line)
sub(/[[:space:]]+\(offset [0-9]+\)$/, "", line)
print line
in_rpath = 0
}
')
while IFS= read -r rpath; do
[[ -z "$rpath" ]] && continue
if path_has_dot_component "$rpath"; then
echo "path traversal in LC_RPATH for $label: $rpath" >&2
exit 1
fi
case "$rpath" in
/usr/lib|/usr/lib/*|/System/Library|/System/Library/*|@loader_path|@loader_path/*|@executable_path|@executable_path/*) ;;
*)
echo "non-portable LC_RPATH in $label: $rpath" >&2
exit 1
;;
esac
done <<< "$rpaths"

local dependency_output
if ! dependency_output=$(otool -L "$macho"); then
echo "cannot inspect Mach-O dependencies for $label" >&2
exit 1
fi
local dependencies
dependencies=$(printf '%s\n' "$dependency_output" | sed -n '2,$ {
s/^[[:space:]]*//
s/ (compatibility version.*$//
p
}')
while IFS= read -r dependency; do
# otool reports a dylib's install name before its actual dependencies.
if [[ "$label" == "PDFium library" && "$dependency" == "./libpdfium.dylib" ]]; then
continue
fi
if path_has_dot_component "$dependency"; then
echo "path traversal in Mach-O dependency for $label: $dependency" >&2
exit 1
fi
case "$dependency" in
""|/usr/lib/*|/System/Library/*|@rpath/*|@loader_path/*|@executable_path/*) ;;
*)
echo "non-portable Mach-O dependency in $label: $dependency" >&2
exit 1
;;
esac
done <<< "$dependencies"
}

check_macho_portability "Shosai binary" "$binary"
check_macho_portability "PDFium library" "$pdfium"

codesign --verify --deep --strict --verbose=2 "$app"

Expand Down
2 changes: 1 addition & 1 deletion scripts/package-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ cat > "$app/Contents/Info.plist" <<EOF
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>$version</string>
<key>CFBundleVersion</key><string>$version</string>
<key>LSMinimumSystemVersion</key><string>12.0</string>
<key>LSMinimumSystemVersion</key><string>13.0</string>
<key>NSHighResolutionCapable</key><true/>
</dict>
</plist>
Expand Down
Loading
Loading