Skip to content

Fix macOS native CEF bundle layout in PrepareJustCefNative - #4

Open
jensrot wants to merge 2 commits into
futo-org:masterfrom
jensrot:fix/macos-native-cef-bundle
Open

Fix macOS native CEF bundle layout in PrepareJustCefNative#4
jensrot wants to merge 2 commits into
futo-org:masterfrom
jensrot:fix/macos-native-cef-bundle

Conversation

@jensrot

@jensrot jensrot commented Aug 24, 2026

Copy link
Copy Markdown

Problem

On macOS, a completely clean checkout can never run: dotnet build/dotnet run on a consuming app (e.g. Grayjay.Desktop.CEF) fails at startup with:

Unhandled exception occurred: System.Exception: Failed to find justcefnative
   at JustCef.JustCefProcess.Start(String args)

The PrepareJustCefNative MSBuild target in JustCef.csproj downloads the platform-specific CEF archive and copies its contents into <output>/cef/..., preserving the archive's internal structure. That's correct for Linux/Windows, where the archive is a flat binary and cef/<binary> is exactly where JustCefProcess.GenerateSearchPaths() looks.

On macOS, though, the archive is a full .app bundle (Contents/MacOS/, Contents/Frameworks/, Contents/Resources/). Copying that into a folder named cef/ produces cef/Contents/MacOS/justcefnative — but GenerateSearchPaths() only ever checks for justcefnative.app/Contents/MacOS/... or JustCef.app/Contents/MacOS/.... It never checks cef/. So the binary is never found.

It's not just a search-path bug

I first tried the "obvious" fix — teaching GenerateSearchPaths() to also check cef/Contents/MacOS/{binary}. That does let JustCefProcess find and launch the binary, but CEF/Chromium then immediately fails on its own:

[ERROR:base/i18n/icu_util.cc:177] icudtl.dat not found in bundle
[ERROR:base/i18n/icu_util.cc:232] Invalid file descriptor to ICU data received.

...and the child process silently exits. CEF resolves bundle-relative resources (icudtl.dat under Contents/Frameworks/*.framework/Resources, and presumably others) against the real containing directory, which has to actually be named *.app for its own internal bundle-resolution logic to work — a plain cef/ directory with the identical internal layout isn't enough, even once the executable itself is locatable.

Fix

Change PrepareJustCefNative's output folder name based on RID: justcefnative.app/ on macOS (osx-*), unchanged cef/ everywhere else. This means the bundle root is named correctly for both problems at once — GenerateSearchPaths()'s existing (unmodified) justcefnative.app/Contents/MacOS/... check now matches, and CEF's own resource resolution works because the directory really is *.app. No C# changes needed at all.

+    <JustCefOutputSubdir Condition="$(JustCefRid.StartsWith('osx-'))">justcefnative.app</JustCefOutputSubdir>
+    <JustCefOutputSubdir Condition="'$(JustCefOutputSubdir)' == ''">cef</JustCefOutputSubdir>
...
-        <TargetPath>cef/%(_JustCefFiles.RecursiveDir)%(_JustCefFiles.Filename)%(_JustCefFiles.Extension)</TargetPath>
+        <TargetPath>$(JustCefOutputSubdir)/%(_JustCefFiles.RecursiveDir)%(_JustCefFiles.Filename)%(_JustCefFiles.Extension)</TargetPath>

How to reproduce (before this fix)

On a clean macOS checkout of Grayjay.Desktop (which consumes this repo as a submodule):

git clone <Grayjay.Desktop repo>
cd Grayjay.Desktop
git submodule update --init --recursive
cd Grayjay.Desktop.CEF
dotnet run

Result: System.Exception: Failed to find justcefnative, every time, on a first build. Inspecting the output directory shows the bundle sitting at bin/Debug/net8.0/osx-arm64/cef/Contents/MacOS/justcefnative — never a path GenerateSearchPaths() actually checks.

How I verified the fix

  1. Applied this same change directly against the exact JustCef commit pinned by Grayjay.Desktop's submodule.
  2. Removed all bin/obj output for a fully clean rebuild — no manual workarounds, no pre-existing justcefnative.app folder.
  3. dotnet build + dotnet run in Grayjay.Desktop.CEF: output now correctly lands at bin/Debug/net8.0/osx-arm64/justcefnative.app/Contents/MacOS/justcefnative.
  4. App launches cleanly end-to-end: native window opens and gets focus, backend initializes (database, plugins), no icudtl.dat error, no missing-binary error.
  5. Confirmed Linux/Windows behavior is untouched — JustCefRid.StartsWith('osx-') is false for every other RID, so JustCefOutputSubdir still resolves to cef exactly as before.

Scope

Single file, JustCef.csproj, +13/-1 lines. No C# changes. No behavior change for non-macOS RIDs.

PrepareJustCefNative copied the downloaded CEF archive's contents into a
plainly-named cef/ output folder on every platform. On macOS the archive is
actually a full app bundle (Contents/MacOS/, Contents/Frameworks/, etc.), and
both JustCefProcess.GenerateSearchPaths() and CEF's own bundle-relative
resource resolution (e.g. icudtl.dat) require that directory to literally be
named *.app - a plain cef/ folder with identical contents isn't enough.

Copy into justcefnative.app/ on macOS RIDs instead, matching what
GenerateSearchPaths() already expects. Linux/Windows still use cef/,
unchanged.
@jensrot
jensrot force-pushed the fix/macos-native-cef-bundle branch from 4ef1f75 to 7c28a7c Compare August 24, 2026 17:55
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.

1 participant