Skip to content

Let an app load its own libjcocoa.dylib instead of the jar's - #47

Merged
shannah merged 1 commit into
masterfrom
feature/load-bundled-libjcocoa
Nov 11, 2026
Merged

shannah merged 1 commit into
masterfrom
feature/load-bundled-libjcocoa

Conversation

@shannah

@shannah shannah commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Problem

RuntimeUtils always extracts the libjcocoa.dylib inside the jar to a temp file and System.load()s it. An app signed with the hardened runtime and without com.apple.security.cs.disable-library-validation cannot load that copy, because it isn't signed by the app's team. That covers Mac App Store builds and many notarized apps. So the bridge can't be used in a sandboxed App Store app, including on Apple Silicon, which 1.2 otherwise supports.

This came up moving PDF OCR X to Apple Silicon (shannah/pdfocrx#46).

Change

  • New system property ca.weblite.objc.libjcocoa.path (RuntimeUtils.LIBJCOCOA_PATH_PROPERTY). When set, the bridge System.loads that file and never extracts its own; the app bundles libjcocoa.dylib and signs it with the rest of the app. A path that isn't a file fails with an UnsatisfiedLinkError naming the property.
  • Opt-in: with the property unset, nothing changes. I deliberately didn't make it try System.loadLibrary("jcocoa") first, because an app with an older libjcocoa on its library path would silently start loading it. PDF OCR X has exactly that: an x86_64-only copy from before 1.1.
  • README: a short "Hardened runtime and the Mac App Store" section.

Tests

LibjcocoaPathTest starts LoadedLibjcocoa in a JVM of its own, since the library is loaded once per JVM in the static initializer. It then asks dyld (_dyld_get_image_name) which libjcocoa image the process loaded:

  • with the property: exactly the named file;
  • without it: one copy extracted under java.io.tmpdir, the existing behaviour;
  • a path that isn't a file: UnsatisfiedLinkError.

CI

maven.yml pinned DEVELOPER_DIR to Xcode 13.2, which current runners don't have. It now:

  • uses the runner's Xcode and current versions of the actions;
  • runs on macos-15 (Apple Silicon) and macos-15-intel;
  • checks that the release build of libjcocoa.dylib contains both arm64 and x86_64.

Release

After merging this needs a release (1.3) to Maven Central for PDF OCR X to depend on it; I haven't touched the version or the publishing setup.


Generated by Claude Code

The bridge always extracted the libjcocoa.dylib inside its jar to a temp
file and System.load()ed it. An app signed with the hardened runtime and
without disable-library-validation (Mac App Store and many notarized
apps) cannot load that copy, because it is not signed by the app's team.

Setting -Dca.weblite.objc.libjcocoa.path to a libjcocoa.dylib now loads
that file instead, so such an app can bundle the library and sign it
with the rest of the app. With the property unset nothing changes; it is
opt-in so an app that happens to carry an older libjcocoa on its library
path does not start loading it.

LibjcocoaPathTest runs a JVM with and without the property and checks,
through dyld, which libjcocoa image the process loaded.

CI: drop the pinned Xcode 13.2 (gone from the runners), update the
actions, run on Apple Silicon and Intel, and check that the release
build of libjcocoa is universal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZMcbjtKBCyrRMgkgSipea
@shannah
shannah marked this pull request as ready for review November 11, 2026 19:21
@shannah
shannah merged commit bb65599 into master Nov 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants