diff --git a/CHANGELOG.md b/CHANGELOG.md index 91c1b586..6050c51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Source/SourceKittenFramework/library_wrapper.swift b/Source/SourceKittenFramework/library_wrapper.swift index c300e249..4f3a07e6 100644 --- a/Source/SourceKittenFramework/library_wrapper.swift +++ b/Source/SourceKittenFramework/library_wrapper.swift @@ -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) } ?? "" + 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")) + """ } } diff --git a/Tests/SourceKittenFrameworkTests/LoaderTests.swift b/Tests/SourceKittenFrameworkTests/LoaderTests.swift new file mode 100644 index 00000000..5922082e --- /dev/null +++ b/Tests/SourceKittenFrameworkTests/LoaderTests.swift @@ -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")) + } +}