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:
- 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.
- 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:
let mainPath = Bundle.main.bundleURL.appendingPathComponent("CodeEditLanguages_CodeEditLanguages.bundle").path
let buildPath = "/Users/<build-machine>/…/.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:
// CodeLanguage.swift
internal var resourceURL: URL? = Bundle.module.resourceURL
Repro
- Depend on
CodeEditLanguages (directly or via CodeEditSourceEditor).
swift build (no Xcode project).
- Assemble a
.app and copy CodeEditLanguages_CodeEditLanguages.bundle
into Contents/Resources/.
- 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
// 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
- With Defect 1 worked around (bundle located), open any source file in an
editor backed by CodeEditSourceEditor.
- Observe: no syntax colors; text is uniform gray.
- 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.
-
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:
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
-
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:
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.
Summary
When
CodeEditLanguagesis consumed by an app that is built and packagedwith
swift build(plus a hand-rolled bundle-assembly step) rather thanXcode, two resource-resolution problems surface:
SwiftPM-generated
Bundle.moduleaccessor cannot locate the resourcebundle inside a
.appand callsfatalError.load because
CodeLanguage.queryURL(for:)hardcodes a path layout thatonly 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
CodeEditLanguages0.1.20 (pulled transitively viaCodeEditSourceEditor0.15.2).swift buildfrom the command line. No.xcodeproj. The app isassembled into a
.appby a custom script that copies the SwiftPM resourcebundle (
CodeEditLanguages_CodeEditLanguages.bundle) intoMyApp.app/Contents/Resources/.Defect 1 —
Bundle.modulefatalErrors in a distributed.appRoot cause
The SwiftPM-generated accessor
(
.build/…/CodeEditLanguages.build/DerivedSources/resource_bundle_accessor.swift)probes exactly two locations:
mainPathusesBundle.main.**bundleURL**— i.e. the.appwrapper root,not
Contents/Resources. A correctly-formed.appkeeps resources inContents/Resources, so this candidate never matches.buildPathis a hardcoded absolute path on the build machine. Itresolves 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 aCodeLanguage'sresourceURL/ query is accessed.Bundle.module(CodeLanguage.resourceURL) is used here:Repro
CodeEditLanguages(directly or viaCodeEditSourceEditor).swift build(no Xcode project)..appand copyCodeEditLanguages_CodeEditLanguages.bundleinto
Contents/Resources/..app. First access to a language'squeryURL→ crash at theaccessor's
fatalError.Contents/Resources; no crash.fatalError("could not load resource bundle …").Defect 2 — query path assumes the Xcode bundle layout
Root cause
The
"Resources/"segment is unconditionally prepended. That matches thedirectory nesting Xcode produces, but under the SwiftPM command-line build the
grammar files do not live at that relative path from the resolved
resourceURL, soqueryURLpoints at a file that does not exist. The lookupreturns a URL that never resolves, no error is thrown, and highlighting
silently degrades to plain text.
Repro
editor backed by
CodeEditSourceEditor.CodeLanguage.swift.queryURLandFileManager.fileExistson thereturned path — the file is not there.
.scmquery loads; tokens are highlighted.queryURLresolves to a non-existent path; queries silentlyfail.
Proposed fix
Both are small and, importantly, change nothing for Xcode consumers — the
new code paths only engage when the current lookup fails.
Bundle resolution — before falling back to
Bundle.module, probeBundle.main.resourceURL(i.e.Contents/Resources), where aCLI-assembled app copies the SwiftPM resource bundle:
Layout-aware query path — resolve against whichever layout exists on
disk, probing the nested (
Resources/…) layout first so Xcode behavior isbyte-for-byte unchanged, then the flat layout:
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.