edmund-build-and-env
GitHubEdmund仓库的构建与环境配置指南。涵盖从新机器克隆到CI镜像的环境搭建,解决构建失败、代码未生效及启动崩溃问题。包含依赖安装、核心命令解析、build-app.sh脚本流程及调试包构造。
触发场景
安装
npx skills add I7T5/Edmund --skill edmund-build-and-env -g -y
SKILL.md
Frontmatter
{
"name": "edmund-build-and-env",
"description": "Build, environment, and toolchain runbook for the Edmund repo (native macOS Markdown editor; AppKit + TextKit 2, SwiftPM). Load this skill when: setting up the environment from scratch (fresh clone, new machine, CI mirror); a build fails or a \"successful\" build behaves stale (change \"doesn't take\", old code runs, Build complete! but nothing changed); the app crashes on launch or the instant it renders LaTeX; you need to construct the debug bundle (EdmundDbg.app) for live runs; or BEFORE trusting any binary you just built for a visual check or repro run. Covers swift build\/test, build-app.sh anatomy (codesign sealing order, SwiftMath bundle placement), the stale-build disease and its detection, safe launch\/kill hygiene around the user's live instance, and the CI environment."
}
Edmund — build & environment runbook
All paths relative to the repo root. Facts date-stamped 2026-07-05 are volatile — re-verify per the last section.
When NOT to use this skill
| You actually need | Go to |
|---|---|
| The storage==rawSource / TextKit-2-only invariants, render pipeline | edmund-architecture-contract |
| Branch/commit/PR rules, what you may touch | edmund-change-control |
| Diagnosing a bug (not the build) | edmund-debugging-playbook |
| ReproScript / CGEvent live-repro driving | edmund-live-repro-and-diagnostics |
| Launch flags, defaults keys, settings | edmund-config-and-flags |
| Cutting a release, DMG, Sparkle appcast | edmund-release-and-operate |
| Screencapture verification method, test policy | edmund-validation-and-qa |
1. Environment from scratch
Requirements (verified 2026-07-05):
| Tool | Version | Why | Check |
|---|---|---|---|
| macOS | 14+ | Package.swift platforms .macOS(.v14) |
sw_vers |
| Xcode | 16+ (full Xcode, not just CLT) | swift-tools-version: 6.0; build-app.sh needs actool |
swift --version (local: Swift 6.0.3) |
| gh CLI | any recent | releases, PR ops | gh --version |
| Node | ≥20 | releases only | node --version |
| create-dmg | npm package | releases only | npm install --global create-dmg |
create-dmg trap: install via npm, NOT Homebrew. brew install create-dmg
is a different tool with an incompatible CLI. Not needed for dev work —
only for cutting releases (see edmund-release-and-operate).
Dependencies are fetched by SPM on first build — nothing to install by hand
(verified against Package.swift / Package.resolved, 2026-07-05):
swift-markdown≥0.5.0 (CommonMark/GFM parsing; pullsswift-cmarktransitively)SwiftMath≥1.7.0 (LaTeX rendering — its resource bundle is a launch-crash landmine, §3)Sparkle≥2.6.0 (auto-update — its framework is a dyld-abort landmine, §4)
Two SPM targets: EdmundCore (library + all tests; most work happens here)
and edmd (the app shell executable). The binary is named edmd even
though the app presents as "Edmund" — deliberate, see the comment in
Package.swift.
2. Core commands
swift build # debug build of both targets
swift test # full suite: ~750+ tests, ~10s (2026-07-05)
swift test --filter Callout # one suite
./scripts/build-app.sh # release build → build/Edmund.app
swift test also runs automatically as a Stop hook after code-touching turns.
3. What build-app.sh actually does (and why the order matters)
scripts/build-app.sh → build/Edmund.app. Steps, in order:
swift build -c release- Assemble
build/Edmund.app/Contents/{MacOS,Resources}; copy.build/release/edmd,Info.plist,Resources/AppIcon.icns. - Compile
Resources/Assets.xcassetswithactool(falls back to/Applications/Xcode.app/.../actoolif xcode-select points at the CLT). - Embed
Sparkle.frameworkintoContents/Frameworks/(found under.build/; SwiftPM links Sparkle but never copies the framework — without it the updater crashes on first check) andinstall_name_tool -add_rpath "@executable_path/../Frameworks"so@rpathresolves post-install. - Codesign inside-out: Sparkle.framework first (nested XPC helpers must
be signed before macOS will launch them), then the whole
.app(ad-hoc,--deep, identifiercom.i7t5.edmd). Sealing the bundle — not just the binary — is what Sparkle's update validator requires. - Only after sealing: copy
.build/release/*.bundle(SwiftMath's math fonts) into the.approot.
Why step 6 is last and at the root — two constraints collide:
codesignrefuses to seal a bundle with any extra item at the.approot ("unsealed contents present in the bundle root"), so the seal must happen while the root holds onlyContents/.- SwiftMath's generated
Bundle.moduleaccessor hardcodesBundle.main.bundleURL— the.approot — with only a hardcoded absolute.buildpath as fallback. So the bundle must sit at the root.
Resolution: seal first, copy after. The one unsealed root item makes
codesign --verify and --strict complain, but Sparkle's actual check is
non-strict and tolerates it (verified end-to-end; details in
docs/ARCHITECTURE.md §8).
Missing SwiftMath bundle = instant crash the moment the app renders any
LaTeX. App launches fine, opens documents fine, dies on the first math
block. If you see that crash, check ls build/Edmund.app/*.bundle first.
4. Debug bundle fast path (EdmundDbg.app)
For live runs of a debug build, skip build-app.sh and hand-assemble
(from docs/dev-guides/live-repro-guide.md §4):
swift build
mkdir -p build/EdmundDbg.app/Contents/MacOS
cp Info.plist build/EdmundDbg.app/Contents/
cp .build/arm64-apple-macosx/debug/edmd build/EdmundDbg.app/Contents/MacOS/
cp -R .build/arm64-apple-macosx/debug/Sparkle.framework build/EdmundDbg.app/Contents/MacOS/
- Sparkle.framework must sit next to the binary — dyld aborts without it.
- A bare
.build/debug/edmdruns but never creates a window. It needs the bundle (Info.plist) around it. - Launch by direct exec of the bundle binary, never
open -a:
build/EdmundDbg.app/Contents/MacOS/edmd /path/to/test.md \
-ApplePersistenceIgnoreState YES &
LaunchServices (open -a) can silently run a stale cached/translocated
copy — you'd be executing last hour's code. Direct exec runs exactly the
binary you just copied. -ApplePersistenceIgnoreState YES stops state
restoration from reopening previous (possibly mutated) documents.
- Recreate the test document fresh before every run — autosave mutates it.
5. THE STALE BUILD DISEASE
The single most expensive trap in this repo: it has produced entire wrong debugging conclusions ("my fix doesn't work" when the fix was never in the binary).
Symptom: swift build prints Build complete! having compiled a changed
file but not relinked edmd. The app then runs old code. Release builds
(swift build -c release / build-app.sh) reuse stale objects too.
Detection — before trusting ANY binary you just built:
# 1. Grep for a LONG string literal unique to your new code:
strings .build/arm64-apple-macosx/debug/edmd | grep 'your long unique literal'
# 2. Hash before/after the build:
shasum .build/arm64-apple-macosx/debug/edmd
Literal length matters: string literals ≤15 bytes are stored inline in
the Mach-O on arm64 and never appear in strings output. A short probe
literal gives a false "stale" verdict. Use a long one (a distinctive log
message works well).
Cure:
swift package clean # first resort
rm -rf .build # visual change "doesn't take" → nuke it all
Never hand-delete .build/…/edmd.build/ — that corrupts SwiftPM's
output-file-map and wedges the target until a full clean anyway.
6. Running for visual checks — launch/kill hygiene
The user's daily-driver app has the same binary name (edmd). A blanket
pkill -x edmd kills their live session. Always, in order:
# 1. Who is running, and since when?
pgrep -lx edmd
ps -o lstart=,command= -p <pid>
# 2. Kill ONLY your own PID — or, if you launched the debug bundle:
pkill -f EdmundDbg
Other run gotchas:
open Edmund.appforegrounds a running instance instead of relaunching — you'll be looking at the old binary. Kill your instance first or direct-exec the binary.- Always pass
-ApplePersistenceIgnoreState YES(see §4). - After many rapid launch/kill cycles the window server can glitch (tiny
windows, broken state restoration):
rm -rf ~/Library/"Saved Application State"/com.i7t5.edmund.savedStateand relaunch. - Verification method (window-id screencapture, offscreen render fallback):
edmund-validation-and-qaanddocs/ARCHITECTURE.md§8.
7. CI environment
.github/workflows/ci.yml (verified 2026-07-05): runs swift test on
macos-14 with latest-stable Xcode (Swift 6.0 needs Xcode 16+), triggered
on PRs and pushes to main.
- SPM cache:
.buildis cached keyed onspm-v2-${{ runner.os }}-${{ hashFiles('Package.resolved') }}. Thev2token exists because the repo renamemd→Edmundchanged the checkout path and invalidated absolute paths baked into the cached module cache — bump the token to discard a poisoned cache. - Concurrency:
cancel-in-progress: trueper branch/PR — private-repo macOS minutes bill at 10x, so superseded commits' runs are cancelled. - Release pipeline (
release.yml, tag-triggered) is a separate beast:edmund-release-and-operate/docs/ARCHITECTURE.md§13.
8. Checklists
Fresh clone to green
-
sw_vers— macOS 14+;swift --version— Swift 6.x (Xcode 16+) -
git clone+cd Edmund -
swift build— SPM fetches swift-markdown, SwiftMath, Sparkle -
swift test— ~750+ tests green in ~10s - Read
docs/ARCHITECTURE.md(mandated by AGENTS.md before non-trivial work) - Visual work planned?
./scripts/build-app.sh, confirmbuild/Edmund.appexists andls build/Edmund.app/*.bundleshows the SwiftMath bundle - Releases planned?
node --version≥20,npm install --global create-dmg
Before trusting any run
- Binary is fresh:
strings <binary> | grep '<long unique literal>'and/orshasumchanged since the edit (§5) -
pgrep -lx edmd— user's live instance identified; you will kill only your own PID (§6) - Launched by direct exec, not
open -a(§4) -
-ApplePersistenceIgnoreState YESpassed - Test document recreated fresh (autosave mutated the last one)
- Debug bundle: Sparkle.framework sits next to the binary
- Release bundle: SwiftMath
*.bundleat the.approot (or the first LaTeX render crashes)
Provenance and maintenance
Every claim above was read from the files below on 2026-07-05. Re-verify:
- Toolchain/deps:
cat Package.swift(tools-version, platforms, dep versions);grep identity Package.resolved - Build anatomy:
cat scripts/build-app.sh(step order, sealing comments) - Test count/time:
swift test 2>&1 | tail -3 - Debug bundle recipe:
docs/dev-guides/live-repro-guide.md§4 - Stale-build disease, launch gotchas:
docs/ARCHITECTURE.md§8 ("Stale release builds", "open Edmund.app", savedState) - CI facts:
cat .github/workflows/ci.yml(cache key comment, concurrency) - create-dmg quirks:
docs/ARCHITECTURE.md§8 ("create-dmg — npm only")
If a command here disagrees with those files, the files win — update this skill.
版本历史
- 4638fdb 当前 2026-07-11 18:03


