Repository navigation
Shrink release DMG 102MB -> 61MB: strip binaries, LZMA DMG, dead-code stripping - #7422
lawrencecchen wants to merge 3 commits into
Conversation
… stripping The shipped app was never stripped: xcodebuild's `build` action skips strip even in Release, so the main binary carried a 59.5MB symbol table per arch (larger than its own __TEXT) and the CLI another 15MB per arch. dSYMs are built separately (dwarf-with-dsym) and uploaded to Sentry, so these symbols were pure download weight. New scripts/strip-app-binaries.sh strips the app binary and CLI with -rSTx and the nucleo FFI dylib and DockTile plugin with -x (exports preserved), wired into release.yml, nightly.yml, and build-sign-upload.sh after the final app payload is assembled and before codesigning. The DMG also moves from create-dmg's ULFO (lzfse) default to ULMO (LZMA) via hdiutil convert before DMG codesign/notarize/staple; appcast signature, length, and Homebrew sha256 all still derive from the final bytes. DEAD_CODE_STRIPPING=YES is set in both project-level configs (previously unset, so linker default NO retained unreferenced GhosttyKit/static code). Debug too, so PR CI exercises dead-stripped links before a release does. Measured on the real v0.64.17 artifact: strip 357MB->221MB installed, lipo archs intact, all 5 FFI dylib exports intact; DMG 102.1MB -> 61.1MB (-40%). ULMO verified mounting via hdiutil attach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR adds a macOS app-binary stripping script, wires it into local build and CI packaging flows, converts generated DMGs to ULMO format before signing, and sets ChangesBinary stripping and DMG packaging
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant BuildScript as build-sign-upload.sh
participant StripScript as strip-app-binaries.sh
participant hdiutil as hdiutil
participant codesign as codesign
BuildScript->>StripScript: strip-app-binaries.sh "$APP_PATH"
StripScript-->>BuildScript: stripped app bundle
BuildScript->>hdiutil: create DMG without codesign
BuildScript->>hdiutil: convert DMG to ULMO
hdiutil-->>BuildScript: cmux-macos.dmg
BuildScript->>codesign: force sign final DMG
Related issues: None found. Related PRs: None found. Suggested labels: build, ci, macos Suggested reviewers: None identified from the provided context. 🐰 A binary trimmed, a DMG refined, 🚥 Pre-merge checks | ✅ 24 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (24 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@codex review |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/build-sign-upload.sh`:
- Around line 119-124: The DMG to ULMO conversion sequence is duplicated across
multiple release paths and should be centralized. Extract the create-dmg,
hdiutil convert, mv, and re-sign flow from the build-sign-upload.sh logic into a
shared script such as scripts/convert-dmg-ulmo.sh, and update the
build-sign-upload.sh, nightly workflow, and release workflow call sites to use
it. Keep the behavior identical, including the post-conversion codesign step, so
all paths stay in sync.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: fd49cee8-6fda-412b-91fa-678c8edf1cc0
📒 Files selected for processing (5)
.github/workflows/nightly.yml.github/workflows/release.ymlcmux.xcodeproj/project.pbxprojscripts/build-sign-upload.shscripts/strip-app-binaries.sh
Greptile SummaryThis PR reduces the macOS release DMG from 102MB to ~61MB by stripping Mach-O symbols, re-compressing the DMG with LZMA (ULMO), and enabling linker dead-code stripping—no feature or architecture changes.
Confidence Score: 5/5Safe to merge — packaging-only changes with no feature or architecture modifications; operation ordering (strip → codesign app → create DMG → ULMO convert → codesign DMG) is correct in all three flows. All three packaging paths (release.yml, nightly.yml, build-sign-upload.sh) apply strip before codesigning the app bundle and ULMO conversion before codesigning the DMG, which is the required order. dSYMs are unaffected so Sentry symbolication continues to work. The -x flag on the dylib and plugin preserves exported symbols, and the PR documents a dlsym audit confirming no self-lookup into the main executable. Dead-code stripping is a standard linker flag with no runtime risk for an app that isn't dynamically looked up by outside callers. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[xcodebuild universal build] --> B[Install Ghostty CLI helper]
B --> C[strip-app-binaries.sh\n-rSTx main exe + CLI\n-x dylib + plugin]
C --> D[sign-cmux-bundle.sh\nCodesign app bundle]
D --> E[xcrun notarytool\nNotarize app]
E --> F[create-dmg\nCreate ULFO DMG]
F --> G[convert-dmg-ulmo.sh\nhdiutil convert ULMO/LZMA]
G --> H[codesign DMG\nSign once after conversion]
H --> I[xcrun notarytool\nNotarize DMG]
I --> J[xcrun stapler\nStaple ticket]
J --> K[Upload release artifact\n102MB to 61MB]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[xcodebuild universal build] --> B[Install Ghostty CLI helper]
B --> C[strip-app-binaries.sh\n-rSTx main exe + CLI\n-x dylib + plugin]
C --> D[sign-cmux-bundle.sh\nCodesign app bundle]
D --> E[xcrun notarytool\nNotarize app]
E --> F[create-dmg\nCreate ULFO DMG]
F --> G[convert-dmg-ulmo.sh\nhdiutil convert ULMO/LZMA]
G --> H[codesign DMG\nSign once after conversion]
H --> I[xcrun notarytool\nNotarize DMG]
I --> J[xcrun stapler\nStaple ticket]
J --> K[Upload release artifact\n102MB to 61MB]
Reviews (3): Last reviewed commit: "Document create-dmg variant difference a..." | Re-trigger Greptile |
| echo "Creating DMG..." | ||
| rm -f cmux-macos.dmg | ||
| rm -f cmux-macos.dmg cmux-macos-ulmo.dmg | ||
| create-dmg --codesign "$SIGN_HASH" cmux-macos.dmg "$APP_PATH" |
There was a problem hiding this comment.
create-dmg --codesign now signs a ULFO DMG that is immediately discarded by hdiutil convert, so the initial signature is never used. The re-sign after mv is what actually matters. The CI workflows already use --no-code-sign for this reason. Switching the local script to match avoids a wasted signing round-trip and keeps the intent clear.
| create-dmg --codesign "$SIGN_HASH" cmux-macos.dmg "$APP_PATH" | |
| create-dmg --no-code-sign cmux-macos.dmg "$APP_PATH" |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 39f2ac4, with one correction: build-sign-upload.sh uses the Homebrew (andreyvit) create-dmg, which has no --no-code-sign flag (that flag belongs to the npm create-dmg the workflows use), so the literal suggestion would have errored. Omitting --codesign is the equivalent there; the DMG is signed once, after the ULMO conversion.
— Claude Code
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Review feedback (CodeRabbit, Greptile): the DMG->ULMO convert/mv sequence was duplicated in three release-critical paths, and build-sign-upload.sh signed a DMG via create-dmg --codesign that hdiutil convert immediately discarded. Centralize the conversion in scripts/convert-dmg-ulmo.sh and sign the DMG once, after conversion. Note the Homebrew create-dmg used by build-sign-upload.sh has no --no-code-sign flag (that is the npm create-dmg in the workflows); omitting --codesign is the equivalent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Homebrew create-dmg used by this manual release path signs opt-in via --codesign (no --no-code-sign flag exists), unlike the npm create-dmg in the CI workflows which signs by default. Three reviewers flagged the omitted signing flag based on the npm tool's semantics; name the invariant inline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The download grew from 12.7MB (v0.60.0) to 102.1MB (v0.64.17). Two structural causes, both fixed here with no feature or architecture change.
Binaries were never stripped. The release workflows use xcodebuild's
buildaction, which skips the strip phase even in Release, so every shipped Mach-O carried its full symbol table: the main binary's arm64 slice has a 59.5MB__LINKEDIT(360k symbols), larger than its 57.8MB__TEXT. dSYMs are built separately (dwarf-with-dsym) and uploaded to Sentry, so server-side crash symbolication is unaffected. Newscripts/strip-app-binaries.shstrips the app binary andResources/bin/cmuxwith-rSTx, and the nucleo FFI dylib and DockTile plugin with-x(exports preserved; verified all 5cmux_nucleo_*exports survive). It hard-fails on missing paths so bundle-layout drift is caught at release time. Wired intorelease.yml,nightly.yml, andbuild-sign-upload.shafter the final app payload is assembled (ghostty helper installed) and before codesigning. The zig-built ghostty helper, Sparkle, and Sentry are already stripped and untouched.DMG used lzfse.
create-dmgemits ULFO; converting to ULMO (LZMA) viahdiutil convertbefore DMG codesign/notarize/staple cuts another 29MB. Appcast edSignature/length and the Homebrew cask sha256 all derive from the final converted bytes. ULMO needs macOS 10.15+ to mount; the app requires Ventura.Also sets
DEAD_CODE_STRIPPING = YESin both project-level configs (previously unset, so the linker default NO kept unreferenced GhosttyKit/static-lib code). It applies to Debug too, so this PR's CI exercises dead-stripped links before any release does. Adlsymaudit found only FFI-dylib and Security.framework lookups, no self-dlsym into the main executable.Measured on the real v0.64.17 artifact (strip script + ULFO->ULMO conversion run locally, DMG rebuilt): app 357MB -> 221MB installed,
lipo -archsintact on both binaries, DMG 102.1MB -> 61.1MB (-40%), converted image mounts viahdiutil attach. Intel support (universal binaries, #2287) is deliberately unchanged.🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Note
Medium Risk
Changes affect every shipped macOS binary and DMG signing order; stripping is mitigated by separate dSYMs for Sentry, but aggressive strip/dead-code settings could surface link or runtime issues if exports or dynamic lookups were missed.
Overview
Shrinks macOS release and nightly downloads by stripping symbol tables from key bundled Mach-Os, enabling linker dead-code stripping, and recompressing disk images with LZMA.
Adds
scripts/strip-app-binaries.shand runs it inrelease.yml,nightly.yml, andscripts/build-sign-upload.shafter the app payload is assembled (including the Ghostty helper) and before codesign/notarize. It strips the main app and CLI with-rSTxand lighter-xon the nucleo FFI dylib and DockTile plugin, failing fast if expected paths are missing.Adds
scripts/convert-dmg-ulmo.shto convert DMGs to ULMO viahdiutil; CI and the manual release script call it aftercreate-dmgand codesign the DMG once afterward (conversion invalidates any prior DMG signature).build-sign-upload.shstops signing at DMG creation time for the same reason.Sets
DEAD_CODE_STRIPPING = YESon project-level Debug and Release incmux.xcodeprojso the linker drops unreferenced code at link time.Reviewed by Cursor Bugbot for commit d09834f. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Shrink macOS release size by ~40%: DMG 102MB → 61MB and installed app 357MB → 221MB. Achieved by stripping binaries, enabling dead-code stripping, and converting the DMG to LZMA; no feature or architecture changes.
scripts/strip-app-binaries.sh; strips app and CLI (-rSTx), FFI dylib and DockTile plugin (-x); run before codesign inrelease.yml,nightly.yml, andscripts/build-sign-upload.sh.scripts/convert-dmg-ulmo.sh; workflows and manual script now convert to ULMO (LZMA) and sign the DMG once after conversion; appcast/Homebrew hashes use the final image; requires macOS 10.15+; clarifies Homebrew vs npmcreate-dmgsigning behavior, so the manual path omits--codesignbefore conversion on purpose.DEAD_CODE_STRIPPING=YESfor Debug/Release to drop unused code.Written for commit d09834f. Summary will update on new commits.
Summary by CodeRabbit