Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows Support #769

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions Source/SourceKittenFramework/Module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public struct Module {
return nodeModuleName == spmName
}
let inputs = node["inputs"]?.array(of: String.self) ?? []
return inputs.allSatisfy({ !$0.contains(".build/checkouts/") }) && !nodeModuleName.hasSuffix("Tests")
return inputs.allSatisfy({ !$0.contains(".build\\checkouts\\") }) && !nodeModuleName.hasSuffix("Tests")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return inputs.allSatisfy({ !$0.contains(".build\\checkouts\\") }) && !nodeModuleName.hasSuffix("Tests")
#if os(Windows)
let checkoutsPath = #".build\checkouts\"#
#else
let checkoutsPath = ".build/checkouts/"
#endif
return inputs.allSatisfy({ !$0.contains(checkoutsPath) }) && !nodeModuleName.hasSuffix("Tests")

}

guard let moduleCommand = commands.first(where: matchModuleName) else {
Expand Down Expand Up @@ -92,7 +92,7 @@ public struct Module {
*/
public init?(spmArguments: [String], spmName: String? = nil, inPath path: String = FileManager.default.currentDirectoryPath) {
fputs("Running swift build\n", stderr)
let buildResults = Exec.run("/usr/bin/env", ["swift", "build"] + spmArguments, currentDirectory: path, stderr: .merge)
let buildResults = Exec.run("C:\\Library\\Developer\\Toolchains\\unknown-Asserts-development.xctoolchain\\usr\\bin\\swift.exe", ["build"] + spmArguments, currentDirectory: path, stderr: .merge)
guard buildResults.terminationStatus == 0 else {
let file = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("swift-build-\(UUID().uuidString).log")
_ = try? buildResults.data.write(to: file)
Expand Down
64 changes: 63 additions & 1 deletion Source/SourceKittenFramework/library_wrapper.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,62 @@
import Foundation
#if os(Windows)
import WinSDK

@_transparent
internal func MAKELANGID(_ p: WORD, _ s: WORD) -> DWORD {
return DWORD((s << 10) | p)
}

struct WindowsError {
compnerd marked this conversation as resolved.
Show resolved Hide resolved
public let code: DWORD
}

extension WindowsError: CustomStringConvertible {
public var description: String {
let dwFlags: DWORD = DWORD(FORMAT_MESSAGE_ALLOCATE_BUFFER)
| DWORD(FORMAT_MESSAGE_FROM_SYSTEM)
| DWORD(FORMAT_MESSAGE_IGNORE_INSERTS)
let dwLanguageId: DWORD =
MAKELANGID(WORD(LANG_NEUTRAL), WORD(SUBLANG_DEFAULT))

var buffer: UnsafeMutablePointer<WCHAR>?
let dwResult = withUnsafeMutablePointer(to: &buffer) {
$0.withMemoryRebound(to: WCHAR.self, capacity: 2) {
FormatMessageW(dwFlags, nil, code, dwLanguageId, $0, 0, nil)
}
}
guard dwResult > 0, let message = buffer else { return "Unknown error" }
defer { LocalFree(message) }
return String(decodingCString: message, as: UTF16.self)
}
}
#endif

// MARK: - Shared Types & Functions

struct DynamicLinkLibrary {
fileprivate let handle: UnsafeMutableRawPointer
#if os(Windows)
typealias HandleType = HMODULE?
#else
typealias HandleType = UnsafeMutableRawPointer
#endif

fileprivate let handle: HandleType

func load<T>(symbol: String) -> T {
#if os(Windows)
if let sym = GetProcAddress(handle, symbol) {
return unsafeBitCast(sym, to: T.self)
}
let error = WindowsError(code: GetLastError())
fatalError("Finding symbol \(symbol) failed: \(error)")
#else
if let sym = dlsym(handle, symbol) {
return unsafeBitCast(sym, to: T.self)
}
let errorString = String(validatingUTF8: dlerror())
fatalError("Finding symbol \(symbol) failed: \(errorString ?? "unknown error")")
#endif
}
}

Expand All @@ -23,9 +69,15 @@ struct Loader {
// try all fullPaths that contains target file,
// then try loading with simple path that depends resolving to DYLD
for fullPath in fullPaths + [path] {
#if os(Windows)
if let handle = fullPath.withCString(encodedAs: UTF16.self, LoadLibraryW) {
return DynamicLinkLibrary(handle: handle)
}
#else
if let handle = dlopen(fullPath, RTLD_LAZY) {
return DynamicLinkLibrary(handle: handle)
}
#endif
}

fatalError("Loading \(path) failed")
Expand Down Expand Up @@ -101,6 +153,16 @@ let toolchainLoader = Loader(searchPaths: [
linuxDefaultLibPath
].compactMap({ $0 }))

#elseif os(Windows)

// MARK: - Windows

let toolchainLoader = Loader(searchPaths: [
"C:\\Library\\Developer\\Toolchains\\unknown-Asserts-development.xctoolchain\\usr\\bin",
].compactMap {
FileManager.default.fileExists(atPath: $0) ? $0 : nil
})

#else

// MARK: - Darwin
Expand Down
10 changes: 8 additions & 2 deletions Tests/SourceKittenFrameworkTests/ClangTranslationUnitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ class ClangTranslationUnitTests: XCTestCase {
}

private func compare(clangFixture fixture: String) {
let unit = ClangTranslationUnit(headerFiles: [fixturesDirectory + fixture + ".h"],
compilerArguments: ["-x", "objective-c", "-isysroot", sdkPath(), "-I", fixturesDirectory])
let path = URL(fileURLWithPath: fixturesDirectory + fixture + ".h")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is used a lot now: URL(fileURLWithPath: ...).standardizedFileURL.withUnsafeFileSystemRepresentation { String(cString: $0!) }

Can you add some sort of shared helper for this? Open to API design ideas, but as a strawman: FileUtils.unsafeSystemPath(for path: String) -> String

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that the unsafeSystemPath makes sense, perhaps FileUtils.nativePathRepresentation? The withUnsafeFileSystemRepresentation is because the closure receives a UnsafePointer<CChar>, but we are transacting in String at this layer.

.standardizedFileURL
.withUnsafeFileSystemRepresentation { String(cString: $0!) }
let includes = URL(fileURLWithPath: fixturesDirectory)
.standardizedFileURL
.withUnsafeFileSystemRepresentation { String(cString: $0!) }
let unit = ClangTranslationUnit(headerFiles: [path],
compilerArguments: ["-x", "objective-c", "-isysroot", sdkPath(), "-I", includes])
compareJSONString(withFixtureNamed: (fixture as NSString).lastPathComponent, jsonString: unit)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"key.annotated_decl" : "<Declaration>struct DocumentedStruct<\/Declaration>",
"key.column" : 8,
"key.decl_lang" : "source.lang.swift",
"key.doc.full_as_xml" : "<Class file=\"DocInfo.swift\" line=\"28\" column=\"8\"><Name>DocumentedStruct<\/Name><USR>s:7DocInfo16DocumentedStructV<\/USR><Declaration>struct DocumentedStruct<\/Declaration><CommentParts><Abstract><Para>A documented struct<\/Para><\/Abstract><\/CommentParts><\/Class>",
"key.filepath" : "DocInfo.swift",
"key.fully_annotated_decl" : "<decl.struct><syntaxtype.keyword>struct<\/syntaxtype.keyword> <decl.name>DocumentedStruct<\/decl.name><\/decl.struct>",
"key.kind" : "source.lang.swift.decl.struct",
"key.length" : 16,
"key.line" : 28,
"key.modulename" : "DocInfo",
"key.name" : "DocumentedStruct",
"key.offset" : 416,
"key.reusingastcontext" : false,
"key.typename" : "DocumentedStruct.Type",
"key.typeusr" : "$s7DocInfo16DocumentedStructVmD",
"key.usr" : "s:7DocInfo16DocumentedStructV"
}
Loading
Loading