CodeEditApp / CodeEditApp/CodeEditLanguages

Syntax highlighting silently fails (and `Bundle.module` crashes) in SwiftPM CLI-built app bundles

Ouverte
#95 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Swift
Étoiles
133
Forks
55
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

## 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.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Piste de recherche

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.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
macos, swift
Domaine
devtools
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
Calme
Clarté
Clairement spécifiée
Accessibilité débutants
65/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.