Skip to content

macOS port, phase 1: core, forge and harness pass on macOS - #1

Merged
francislavoie merged 12 commits into
mainfrom
mac-port
Oct 8, 2026
Merged

francislavoie merged 12 commits into
mainfrom
mac-port

Conversation

@francislavoie

@francislavoie francislavoie commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

macOS port, phase 1: the core, forge and harness build and pass their tests on macOS (Apple Silicon, macos-15), and cargo check --workspace --all-targets compiles there, gitbolt-app included. Linux and Windows behave as before.

Proof

  • Proof run (on this PR, macos label, no continue-on-error): 37726507007. 2114 tests passed, 0 failed (5 #[ignore] not run); the workspace check passed, gitbolt-app included.
  • Earlier iteration run: 37725448592. 2114 tests, 0 failed; the workspace check passed. Earlier runs set continue-on-error on the test and check steps so a failing push didn't send an email. The proof run above doesn't.

The workflow (.github/workflows/mac-port.yml)

  • Triggers: on demand only. Either workflow_dispatch, or pull_request types: [labeled] gated on the label macos (each time it's added). It doesn't run on pushes, on main, or on ordinary PR events.
  • Jobs on macos-15: cargo nextest run --profile ci -p gitbolt-core -p gitbolt-forge -p gitbolt-harness (JUnit report uploaded), and cargo check --workspace --all-targets (log uploaded). Each writes a failure summary to the run's step summary.
  • Actions are SHA-pinned as in release.yml, with Swatinem/rust-cache.

Core changes

  • Open in… (openers/mac_detect.rs):
    • editors are found on PATH, else inside their app bundles: VS Code, Cursor, VSCodium, Sublime Text, Zed;
    • JetBrains IDEs come from the Toolbox scripts, then PATH, then any .app that holds their launcher;
    • BBEdit, Nova, TextMate and TextEdit open through open -a;
    • Finder uses open, and open -R to reveal a file.
  • Links: they open with /usr/bin/open, through the child-env hook.
  • Login shell: the env capture applies on macOS (Dock apps get launchd's bare PATH). The shell is $SHELL, else the account's own.
  • Diagnostics: the report names the macOS version, read from SystemVersion.plist.
  • Watcher: watch changes are batched through paths_mut. Without that, FSEvents recreated its stream, with every path, on each watch/unwatch call. On inotify the batch is the same calls made one by one.
  • Capped git reads: the select checks Capped before Done. On macOS git often died of SIGPIPE before the select ran, so a capped read showed up as "git exited with status -1". This affected the hex view and the MR description prefill.
  • Stale index.lock: on macOS, the holder is found with lsof (no /proc there).

Tests

  • Made portable:
    • sed -i.orig, touch -t, true from PATH;
    • the parent's command line comes from /proc or ps;
    • the cache path is compared canonically (/var is /private/var);
    • the editor-template read sample uses git (code may not be installed).
  • Not run on macOS, each with a reason:
    • 2 tests that read the session id from /proc: git::a_detached_command_has_its_own_session… and shellenv::the_capture_shell_leads_its_own_session;
    • 1 zbus test;
    • 2 tests with non-UTF-8 file names (APFS refuses them);
    • instance::a_request_sent_before_the_first_instance_serves…. On macOS the peer credentials go away with the peer, so the user check refuses a request whose sender gave up before the first instance served. The delivery half of the simultaneous-launch test is skipped for the same reason.
  • New tests:
    • macOS opener detection (it runs on Linux too);
    • a real /bin/sh env capture;
    • a check that the runtime dir leaves room for the socket path (104 bytes on macOS);
    • the macOS version, on macOS.

Phase 2 (not in this PR)

  • The app: the CEF .app bundle and its helper apps, the sandbox, and packaging as a .dmg with ad-hoc signing.
  • Cmd shortcuts, the menu bar, and opening files through Apple Events.
  • The folder picker and the Open With chooser (both None on macOS for now).
  • e2e on macOS.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Gd8cWxFv1V5tj9RPxWsZNr

francislavoie and others added 10 commits October 7, 2026 23:42
…ace check

On macos-15 (arm64), pushes to mac-port and workflow_dispatch. A nextest ci profile keeps a
JUnit report for the run's artifacts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ress

continue-on-error on the test and check steps (no failure email per push), and a step summary
listing the failed tests or the first compile errors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pen with open

mac_detect.rs: launchers on PATH, else inside their bundles (VS Code, Cursor, VSCodium, Sublime
Text, Zed), JetBrains IDEs through the Toolbox scripts, PATH or any bundle holding their
launcher, editors without one (BBEdit, Nova, TextMate, TextEdit) through open -a, and Finder
(open, open -R to show a file). URLs open with /usr/bin/open through the child-env hook. The
Open With chooser and the folder picker stay None on macOS (phase 2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ame the macOS version

An app started from the Dock gets launchd's bare PATH; the capture fixes that, with $SHELL or
else the account's own shell. A real-shell capture test checks the system's env -0 (BSD's on
macOS); the session-id test reads /proc, so it's Linux only. Diagnostics read
SystemVersion.plist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…am per call)

notify's FsEventWatcher stops and recreates its stream, with every path, on each watch and
unwatch; paths_mut batches them. inotify's batch is the same calls one by one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reader closes stdout once it has enough; on macOS git often exits on SIGPIPE before the
select runs, and the biased select took that exit as a failure (status -1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
No /proc there: lsof finds a process with the lock open, or a git process whose cwd is in the
repository. The test waits for its holder through a marker file instead of /proc.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- sed -i.orig (GNU and BSD), touch -t, true from PATH, and a parent's command line from /proc
  or ps;
- the video cache path compared canonically (/var is /private/var);
- the editor-template read sample uses git, which every test machine has (code may be missing);
- non-UTF-8 file names gated off macOS (APFS refuses them), as on Windows;
- a request forwarded before the first instance serves isn't checked on macOS: its peer
  credentials go with the peer there;
- the runtime dir leaves room for the instance socket's path (104 bytes on macOS).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…acos), failures fail

Drops the push trigger and continue-on-error; the step summaries stay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@francislavoie francislavoie added the macos Run the macOS workflow on this PR label Oct 8, 2026
francislavoie and others added 2 commits October 8, 2026 00:24
~/Library/Application Support/GitBolt and ~/Library/Caches/GitBolt, as macOS
names apps' folders; gitbolt elsewhere, as before (paths::APP_DIR).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gd8cWxFv1V5tj9RPxWsZNr
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gd8cWxFv1V5tj9RPxWsZNr
@francislavoie
francislavoie merged commit 718681d into main Oct 8, 2026
3 checks passed
@francislavoie
francislavoie deleted the mac-port branch October 8, 2026 04:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macos Run the macOS workflow on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant