Skip to content
Open
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
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
## Main

#### Breaking

* None.

#### Enhancements

* Include each failed `dlopen`/`LoadLibrary` attempt and the underlying
`dlerror()` / `GetLastError()` reason in the fatal error message emitted
when `sourcekitdInProc` cannot be loaded. Previously the error was just
`Loading X failed`, which forced downstream users (e.g. SwiftLint) to
guess at the root cause; common failures such as architecture mismatch
(`mach-o file, but is an incompatible architecture`) are now surfaced
directly.
[Daniel Sunarjo](https://github.com/sunarjodaniel)
[realm/SwiftLint#6475](https://github.com/realm/SwiftLint/issues/6475)

#### Bug Fixes

* None.

## 0.37.3

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