Files
sx/issues/0051-macos-bundle-assets-cwd-relative.md
agra b9cfe2554f refactor(ffi-linkage): Phase 9.3/9.4 — purge 'foreign' from issues/*.md; GATE PASS
Rewrote 20 issue writeups to the extern/runtime-class vocabulary (#foreign→extern,
foreign_class_map→runtime_class_map, parseForeignClassDecl→parseRuntimeClassDecl,
findForeignMethodInChain→findRuntimeMethodInChain, dedupeForeignSymbol→
dedupeExternSymbol, is_foreign_c_api→is_extern_c_api, stale filename refs to the
renamed examples, foreign-class→runtime-class, bare foreign→extern). Renamed
issues/0043-…-foreign-class-…→…-runtime-class-….

PHASE 9 COMPLETE — 9.4 GATE PASSES: zero 'foreign' across src/library/examples/
issues/docs/editors/specs/readme/CLAUDE, excluding only the SQLite API constant
SQLITE_CONSTRAINT_FOREIGNKEY + vendored sqlite3.c/.h (upstream third-party).
Suite green (644 corpus / 443 unit, 0 failed).
2026-06-15 11:18:35 +03:00

5.8 KiB

FIXED. Two parts, both needed for Finder/open to work:

  1. cwd-relative assetsSdlPlatform.init now chdir's to SDL_GetBasePath() when running from inside a .app (sx commit b31fbae, library/modules/platform/sdl3.sx), mirroring uikit.sx's iOS chdir_to_bundle. Gated to the .app case so the sx run dev flow keeps the project CWD.
  2. Gatekeeper-rejected signature — the bundle was signed with a Development cert, which spctl rejects, so a standalone open wouldn't launch it. Switched the macOS build to ad-hoc signing (the bundler's macOS/sim default — just leave the codesign identity empty; game commit d80a350, build.sx).

Verified: a plain cd game && sx build main.sx then open sx-out/macos/SxChess.app launches and renders (was: instant stbtt segfault on missing font, or no launch at all). It was NOT App Translocation — the bundle had no com.apple.quarantine xattr.

Symptom

A bundled macOS .app built with sx build crashes on launch when started via Finder double-click or open Foo.app, but runs fine when launched from a shell whose CWD is the bundle directory.

Observed: open sx-out/macos/SxChess.app → process exits within ~1s (segfaults inside stbtt_ScaleForPixelHeight because the font buffer is null — the asset wasn't found). Expected: double-click / open launches the app and it finds its bundled assets, same as on iOS.

Root: assets are loaded with CWD-relative paths (e.g. "assets/fonts/default.ttf"), but Finder/open start a GUI app with CWD=/, so the relative path resolves against / and the file is missing.

Reproduction

Any consumer that bundles an assets/ dir and loads from it by relative path. Minimal shape (real case: /Users/agra/projects/game):

// main.sx — loads assets by CWD-relative path
g_pipeline.init_font("assets/fonts/default.ttf", 32.0, dpi);   // -> read_file_bytes
g_chess_game.pieces.load("assets/chess/pieces.png", gpu);      // -> read_file_bytes
cd game && sx build main.sx          # produces sx-out/macos/SxChess.app (assets copied in)

# Works (CWD = bundle dir, so "assets/..." resolves):
cd sx-out/macos/SxChess.app && ./SxChess

# Fails (CWD = /, asset not found -> null buffer -> stbtt segfault):
open sx-out/macos/SxChess.app
# or double-click in Finder

add_asset_dir("assets", "assets") in build.sx correctly copies the tree into the flat .app (binary at SxChess.app/SxChess, assets at SxChess.app/assets/...), so the files ARE present in the bundle — they're just not found because the lookup is CWD-relative and CWD isn't the bundle.

Root cause

The macOS SDL startup never reorients CWD (or the asset root) to the bundle. SdlPlatform.init (library/modules/platform/sdl3.sx:35) calls SDL_Init(SDL_INIT_VIDEO) and creates the window but does no chdir, so read_file_bytes (library/modules/ui/glyph_cache.sx:202, and the chess extern read_file_bytes) opens paths relative to whatever CWD the launcher set — / under Finder/open.

The other platforms already handle this:

macOS has neither — so it only works by accident when launched from a shell sitting in the bundle dir.

Investigation prompt

For a fresh session picking this up:

The fix mirrors the iOS precedent. In SdlPlatform.init (sdl3.sx:35), before any asset is loaded, reorient to the bundle's resource directory on macOS only (leave wasm/emscripten and the dev sx run path alone — those legitimately want the project CWD).

Recommended approach — SDL_GetBasePath():

  • SDL3 SDL_GetBasePath() returns the directory containing the executable (for a .app, that's SxChess.app/ where the assets were copied). chdir to it at the top of init when BuildOptions.is_macos (gate so sx run during development isn't affected — or gate on "the base path differs from CWD and contains an assets/ dir").
  • Add the extern decl for SDL_GetBasePath (returns *u8, SDL-owned) and call chdir (already used by uikit.sx — reuse the same extern).

Alternative (no SDL dependency): _NSGetExecutablePath + dirname, same as a plain macOS resolve. SDL_GetBasePath is simpler and already links SDL3.

Things to verify / watch:

  • Don't chdir for the sx run <file> (JIT) dev flow or for wasm — only the bundled AOT macOS app. The cleanest gate is the bundle context; if init can't see BuildOptions, gate on SDL_GetBasePath() returning a path that ends in .app/ (bundled) vs the build dir (dev).
  • After chdir, the existing "assets/..." relative loads resolve unchanged — no call-site changes needed in consumers (chess, glyph_cache).
  • Confirm the iOS path (uikit, doesn't use SdlPlatform) is untouched.

Verification

cd game && sx build main.sx
open sx-out/macos/SxChess.app            # must launch and stay up (board + pieces render)
# screenshot / pgrep -lf sx-out/macos/SxChess  -> alive after a few seconds

Before the fix: open → exits ~1s (stbtt segfault, null font buffer). After: open and Finder double-click both launch and render, matching the cd bundle && ./SxChess behavior.

Also re-run host + cross suites to confirm no platform-module regression: zig build && zig build test && bash tests/run_examples.sh.