Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

* New syntax, attribute and declaration kinds introduced in Swift 6.1-6.3.
[John Fairhurst](https://github.com/johnfairh)
* Improve reporting of `sourcekitdInProc` loading failures.
[Daniel Sunarjo](https://github.com/sunarjodaniel)

#### Bug Fixes

Expand Down
19 changes: 18 additions & 1 deletion Source/SourceKittenFramework/library_wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,36 @@ struct Loader {

// try all fullPaths that contains target file,
// then try loading with simple path that depends resolving to DYLD
var attemptFailures: [String] = []
for fullPath in fullPaths + [path] {
#if os(Windows)
if let handle = fullPath.withCString(encodedAs: UTF16.self, LoadLibraryW) {
return DynamicLinkLibrary(handle: handle)
}
attemptFailures.append(" \(fullPath): GetLastError=\(GetLastError())")
#else
if let handle = dlopen(fullPath, RTLD_LAZY) {
return DynamicLinkLibrary(handle: handle)
}
// dlerror() must be read immediately after a failed dlopen; it clears on read.
let reason = dlerror().flatMap { String(validatingUTF8: $0) } ?? "<no error reported by dlerror>"
attemptFailures.append(" \(fullPath): \(reason)")
#endif
}

fatalError("Loading \(path) failed")
fatalError(Loader.failureMessage(path: path, attemptFailures: attemptFailures))
}

/// Builds the diagnostic emitted when no candidate path could be loaded.
/// Extracted so the message can be exercised in tests without triggering a fatal error.
static func failureMessage(path: String, attemptFailures: [String]) -> String {
guard !attemptFailures.isEmpty else {
return "Loading \(path) failed: no candidate paths were attempted (check searchPaths configuration)."
}
return """
Loading \(path) failed. Tried:
\(attemptFailures.joined(separator: "\n"))
"""
}
}

Expand Down
30 changes: 30 additions & 0 deletions Tests/SourceKittenFrameworkTests/LoaderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@testable import SourceKittenFramework
import XCTest

final class LoaderTests: XCTestCase {

func testFailureMessageIncludesEveryAttemptAndItsReason() {
let message = Loader.failureMessage(
path: "sourcekitdInProc.framework/Versions/A/sourcekitdInProc",
attemptFailures: [
" /Xcode.app/.../usr/lib/sourcekitdInProc.framework/Versions/A/sourcekitdInProc: " +
"mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')",
" sourcekitdInProc.framework/Versions/A/sourcekitdInProc: image not found"
]
)

// The path being loaded is named.
XCTAssertTrue(message.contains("sourcekitdInProc.framework/Versions/A/sourcekitdInProc"))
// Every attempt's dlerror() reason survives into the final message.
XCTAssertTrue(message.contains("incompatible architecture"))
XCTAssertTrue(message.contains("image not found"))
}

func testFailureMessageWhenNoCandidatesWereTried() {
// Empty searchPaths *and* an empty bare path attempt should produce an actionable hint
// rather than a bare "Loading X failed" with no further detail.
let message = Loader.failureMessage(path: "libfoo.so", attemptFailures: [])
XCTAssertTrue(message.contains("libfoo.so"))
XCTAssertTrue(message.contains("searchPaths"))
}
}
Loading