CodeEditApp / CodeEditApp/CodeEditLanguages
Syntax highlighting silently fails (and `Bundle.module` crashes) in SwiftPM CLI-built app bundles
- Lingua principale
- Swift
- Stelle
- 133
- Fork
- 55
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## Summary
When `CodeEditLanguages` is consumed by an app that is **built and packaged
with `swift build` (plus a hand-rolled bundle-assembly step) rather than
Xcode**, two resource-resolution problems surface:
1. **Distributed app crashes** on the first highlight lookup, because the
SwiftPM-generated `Bundle.module` accessor cannot locate the resource
bundle inside a `.app` and calls `fatalError`.
2. Even with the bundle located, **every tree-sitter query file fails to
load** because `CodeLanguage.queryURL(for:)` hardcodes a path layout that
only matches the Xcode-produced bundle. The editor renders as plain gray
text with no error.
Both stem from the same assumption — that the package is always built and
embedded by Xcode. Dev builds on the build machine work, which is why this is
easy to miss.
## Environment
- Package: `CodeEditLanguages` 0.1.20 (pulled transitively via
`CodeEditSourceEditor` 0.15.2).
- Build: `swift build` from the command line. **No `.xcodeproj`.** The app is
assembled into a `.app` by a custom script that copies the SwiftPM resource
bundle (`CodeEditLanguages_CodeEditLanguages.bundle`) into
`MyApp.app/Contents/Resources/`.
- macOS, Apple Silicon, current Swift toolchain.
## Defect 1 — `Bundle.module` fatalErrors in a distributed `.app`
### Root cause
The SwiftPM-generated accessor
(`.build/…/CodeEditLanguages.build/DerivedSources/resource_bundle_accessor.swift`)
probes exactly two locations:
```swift
let mainPath = Bundle.main.bundleURL.appendingPathComponent("CodeEditLanguages_CodeEditLanguages.bundle").path
let buildPath = "/Users//…/.build/arm64-apple-macosx/debug/CodeEditLanguages_CodeEditLanguages.bundle"
let preferredBundle = Bundle(path: mainPath)
guard let bundle = preferredBundle ?? Bundle(path: buildPath) else {
Swift.fatalError("could not load resource bundle: from \(mainPath) or \(buildPath)")
}
```
- `mainPath` uses `Bundle.main.**bundleURL**` — i.e. the `.app` wrapper root,
**not** `Contents/Resources`. A correctly-formed `.app` keeps resources in
`Contents/Resources`, so this candidate never matches.
- `buildPath` is a hardcoded **absolute path on the build machine**. It
resolves during local development, but obviously not on any other machine.
Result on an end user's machine: neither candidate exists →
`Swift.fatalError` → the app crashes the first time a `CodeLanguage`'s
`resourceURL` / query is accessed.
`Bundle.module` (`CodeLanguage.resourceURL`) is used here:
```swift
// CodeLanguage.swift
internal var resourceURL: URL? = Bundle.module.resourceURL
```
### Repro
1. Depend on `CodeEditLanguages` (directly or via `CodeEditSourceEditor`).
2. `swift build` (no Xcode project).
3. Assemble a `.app` and copy `CodeEditLanguages_CodeEditLanguages.bundle`
into `Contents/Resources/`.
4. Run the `.app`. First access to a language's `queryURL` → crash at the
accessor's `fatalError`.
- **Expected:** the bundle is found under `Contents/Resources`; no crash.
- **Actual:** `fatalError("could not load resource bundle …")`.
## Defect 2 — query path assumes the Xcode bundle layout
### Root cause
```swift
// CodeLanguage.swift
internal func queryURL(for highlights: String = "highlights") -> URL? {
return resourceURL?
.appendingPathComponent("Resources/tree-sitter-\(tsName)/\(highlights).scm")
}
```
The `"Resources/"` segment is unconditionally prepended. That matches the
directory nesting Xcode produces, but under the SwiftPM command-line build the
grammar files do **not** live at that relative path from the resolved
`resourceURL`, so `queryURL` points at a file that does not exist. The lookup
returns a URL that never resolves, no error is thrown, and highlighting
silently degrades to plain text.
### Repro
1. With Defect 1 worked around (bundle located), open any source file in an
editor backed by `CodeEditSourceEditor`.
2. Observe: no syntax colors; text is uniform gray.
3. Log `CodeLanguage.swift.queryURL` and `FileManager.fileExists` on the
returned path — the file is not there.
- **Expected:** the `.scm` query loads; tokens are highlighted.
- **Actual:** `queryURL` resolves to a non-existent path; queries silently
fail.
## Proposed fix
Both are small and, importantly, **change nothing for Xcode consumers** — the
new code paths only engage when the current lookup fails.
1. **Bundle resolution** — before falling back to `Bundle.module`, probe
`Bundle.main.resourceURL` (i.e. `Contents/Resources`), where a
CLI-assembled app copies the SwiftPM resource bundle:
```swift
private static let queryBundleResourceURL: URL? = {
if let bundled = Bundle.main.resourceURL?
.appendingPathComponent("CodeEditLanguages_CodeEditLanguages.bundle"),
let bundle = Bundle(url: bundled) {
return bundle.resourceURL
}
return Bundle.module.resourceURL
}()
internal var resourceURL: URL? = CodeLanguage.queryBundleResourceURL
```
2. **Layout-aware query path** — resolve against whichever layout exists on
disk, probing the nested (`Resources/…`) layout first so Xcode behavior is
byte-for-byte unchanged, then the flat layout:
```swift
guard let base = resourceURL else { return nil }
let relativePath = "tree-sitter-\(tsName)/\(highlights).scm"
let nested = base.appendingPathComponent("Resources/\(relativePath)")
if FileManager.default.fileExists(atPath: nested.path) {
return nested
}
return base.appendingPathComponent(relativePath)
```
## Offer to contribute
We are already running these two changes as a local patch against 0.1.20 and
they resolve both issues with no observed regression for Xcode-built consumers.
Per the CodeEdit contribution convention (open an issue first, ask to be
assigned), we'd be glad to submit the PR — could you assign this to us? The
patch passes SwiftLint and keeps the existing code paths as the primary
lookup. Happy to adjust the approach if you'd prefer a different shape.
Thanks for maintaining CodeEditLanguages.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
Start in CodeLanguage.swift, especially resourceURL and queryURL(for:), then reproduce with swift build and a hand-assembled .app containing the named resource bundle. Verify the bundle is found under Contents/Resources, both query layouts work, .scm files load for highlighting, and Xcode-built consumers retain their current behavior; run SwiftLint.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- macos, swift
- Ambito
- devtools
- Tipo di issue
- Bug
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Stato di attività
- Tranquilla
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 65/100