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

Add partial Windows support #779

Merged
merged 15 commits into from
Jun 14, 2023
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
145 changes: 145 additions & 0 deletions Source/SourceKittenFramework/LibraryWrapperGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import Foundation

// MARK: - LibraryWrapperGenerator

/// Generator for SourceKitten's library wrappers.
enum LibraryWrapperGenerator: CaseIterable {
case clang
case sourcekit

/// The relative file path for this library wrapper's generated source file.
var filePath: String {
return "Source/SourceKittenFramework/library_wrapper_\(moduleName).swift"
}

/// Generate the Swift source code for this library wrapper.
///
/// - parameter compilerArguments: The compiler arguments to SourceKittenFramework.
///
/// - returns: The generated Swift source code for this library wrapper.
func generate(compilerArguments: [String]) throws -> String {
let freeFunctions = try extractFreeFunctions(compilerArguments: compilerArguments)
.joined(separator: "\n")
return [
fileHeader,
"// swiftlint:disable unused_declaration - We don't care if some of these are unused.",
freeFunctions,
self == .clang ? "#endif" : nil
]
.compactMap { $0 }
.joined(separator: "\n\n") + "\n"
}
}

// MARK: - Private

private extension LibraryWrapperGenerator {
/// The wrapped module name.
var moduleName: String {
switch self {
case .clang:
return "Clang_C"
case .sourcekit:
return "SourceKit"
}
}

/// The top section of the generated library wrapper.
var fileHeader: String {
switch self {
case .clang:
return """
#if !os(Linux)

#if os(Windows)
import WinSDK
#else
import Darwin
#endif

#if SWIFT_PACKAGE
import Clang_C
#endif

#if os(Windows)
private let library = toolchainLoader.load(path: "libclang.dll")
#else
private let library = toolchainLoader.load(path: "libclang.dylib")
#endif
"""
case .sourcekit:
return """
#if SWIFT_PACKAGE
import SourceKit
#endif

#if os(Linux)
private let library = toolchainLoader.load(path: "libsourcekitdInProc.so")
#elseif os(Windows)
private let library = toolchainLoader.load(path: "sourcekitdInProc.dll")
#else
private let library = toolchainLoader.load(path: "sourcekitdInProc.framework/Versions/A/sourcekitdInProc")
#endif
"""
}
}

func extractFreeFunctions(compilerArguments: [String]) throws -> [String] {
let sourceKitResponse = try interfaceForModule(moduleName, compilerArguments: compilerArguments)
let substructure = SwiftDocKey.getSubstructure(Structure(sourceKitResponse: sourceKitResponse).dictionary)!
let source = sourceKitResponse["key.sourcetext"] as! String
return source.extractFreeFunctions(inSubstructure: substructure)
}
}

private func interfaceForModule(_ module: String, compilerArguments: [String]) throws -> [String: SourceKitRepresentable] {
return try Request.customRequest(request: [
"key.request": UID("source.request.editor.open.interface"),
"key.name": UUID().uuidString,
"key.compilerargs": compilerArguments,
"key.modulename": module
]).send()
}

private extension String {
func nameFromFullFunctionName() -> String {
return String(self[..<range(of: "(")!.lowerBound])
}

func extractFreeFunctions(inSubstructure substructure: [[String: SourceKitRepresentable]]) -> [String] {
substructure
.filter { SwiftDeclarationKind(rawValue: SwiftDocKey.getKind($0)!) == .functionFree }
.compactMap { function -> String? in
let name = (function["key.name"] as! String).nameFromFullFunctionName()
let unsupportedFunctions = [
"clang_executeOnThread",
"sourcekitd_variant_dictionary_apply",
"sourcekitd_variant_array_apply"
]
guard !unsupportedFunctions.contains(name) else {
return nil
}

let parameters = SwiftDocKey.getSubstructure(function)?.map { parameterStructure in
return parameterStructure["key.typename"] as! String
} ?? []
var returnTypes = [String]()
if let offset = SwiftDocKey.getOffset(function), let length = SwiftDocKey.getLength(function) {
let stringView = StringView(self)
if let functionDeclaration = stringView.substringWithByteRange(ByteRange(location: offset, length: length)),
let startOfReturnArrow = functionDeclaration.range(of: "->", options: .backwards)?.lowerBound {
let adjustedDistance = distance(from: startIndex, to: startOfReturnArrow)
let adjustedReturnTypeStartIndex = functionDeclaration.index(functionDeclaration.startIndex,
offsetBy: adjustedDistance + 3)
returnTypes.append(String(functionDeclaration[adjustedReturnTypeStartIndex...]))
}
}

let joinedParameters = parameters.map({ $0.replacingOccurrences(of: "!", with: "?") }).joined(separator: ", ")
let joinedReturnTypes = returnTypes.map({ $0.replacingOccurrences(of: "!", with: "?") }).joined(separator: ", ")
let lhs = "internal let \(name): @convention(c) (\(joinedParameters)) -> (\(joinedReturnTypes))"
let rhs = "library.load(symbol: \"\(name)\")"
return "\(lhs) = \(rhs)".replacingOccurrences(of: "SourceKittenFramework.", with: "")
}
}
}
88 changes: 0 additions & 88 deletions Source/SourceKittenFramework/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -416,91 +416,3 @@ extension Request: CustomStringConvertible {
/// A textual representation of `Request`.
public var description: String { return sourcekitObject.description }
}

private func interfaceForModule(_ module: String, compilerArguments: [String]) throws -> [String: SourceKitRepresentable] {
return try Request.customRequest(request: [
"key.request": UID("source.request.editor.open.interface"),
"key.name": NSUUID().uuidString,
"key.compilerargs": compilerArguments,
"key.modulename": module
]).send()
}

extension String {
private func nameFromFullFunctionName() -> String {
return String(self[..<range(of: "(")!.lowerBound])
}

fileprivate func extractFreeFunctions(inSubstructure substructure: [[String: SourceKitRepresentable]]) -> [String] {
return substructure.filter({
SwiftDeclarationKind(rawValue: SwiftDocKey.getKind($0)!) == .functionFree
}).compactMap { function -> String? in
let name = (function["key.name"] as! String).nameFromFullFunctionName()
let unsupportedFunctions = [
"clang_executeOnThread",
"sourcekitd_variant_dictionary_apply",
"sourcekitd_variant_array_apply"
]
guard !unsupportedFunctions.contains(name) else {
return nil
}

let parameters = SwiftDocKey.getSubstructure(function)?.map { parameterStructure in
return parameterStructure["key.typename"] as! String
} ?? []
var returnTypes = [String]()
if let offset = SwiftDocKey.getOffset(function), let length = SwiftDocKey.getLength(function) {
let stringView = StringView(self)
if let functionDeclaration = stringView.substringWithByteRange(ByteRange(location: offset, length: length)),
let startOfReturnArrow = functionDeclaration.range(of: "->", options: .backwards)?.lowerBound {
let adjustedDistance = distance(from: startIndex, to: startOfReturnArrow)
let adjustedReturnTypeStartIndex = functionDeclaration.index(functionDeclaration.startIndex,
offsetBy: adjustedDistance + 3)
returnTypes.append(String(functionDeclaration[adjustedReturnTypeStartIndex...]))
}
}

let joinedParameters = parameters.map({ $0.replacingOccurrences(of: "!", with: "?") }).joined(separator: ", ")
let joinedReturnTypes = returnTypes.map({ $0.replacingOccurrences(of: "!", with: "?") }).joined(separator: ", ")
let lhs = "internal let \(name): @convention(c) (\(joinedParameters)) -> (\(joinedReturnTypes))"
let rhs = "library.load(symbol: \"\(name)\")"
return "\(lhs) = \(rhs)".replacingOccurrences(of: "SourceKittenFramework.", with: "")
}
}
}

internal func libraryWrapperForModule(_ module: String,
macOSPath: String,
linuxPath: String?,
compilerArguments: [String]) throws -> String {
let sourceKitResponse = try interfaceForModule(module, compilerArguments: compilerArguments)
let substructure = SwiftDocKey.getSubstructure(Structure(sourceKitResponse: sourceKitResponse).dictionary)!
let source = sourceKitResponse["key.sourcetext"] as! String
let freeFunctions = source.extractFreeFunctions(inSubstructure: substructure)
let spmImport = "#if SWIFT_PACKAGE\nimport \(module)\n#endif\n"
let library: String
if let linuxPath = linuxPath {
library = """
#if os(Linux)
private let path = "\(linuxPath)"
#else
private let path = "\(macOSPath)"
#endif
private let library = toolchainLoader.load(path: path)

"""
} else {
library = "private let library = toolchainLoader.load(path: \"\(macOSPath)\")\n"
}
let swiftlintDisableComment = "// swiftlint:disable unused_declaration - We don't care if some of these are unused.\n"
let startPlatformCheck: String
let endPlatformCheck: String
if linuxPath == nil {
startPlatformCheck = "#if !os(Linux)\nimport Darwin\n"
endPlatformCheck = "\n#endif\n"
} else {
startPlatformCheck = ""
endPlatformCheck = "\n"
}
return startPlatformCheck + spmImport + library + swiftlintDisableComment + freeFunctions.joined(separator: "\n") + endPlatformCheck
}
10 changes: 9 additions & 1 deletion Source/SourceKittenFramework/SourceKitObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import Foundation
import SourceKit
#endif

#if os(Linux)
import Glibc
#elseif os(Windows)
import ucrt
#else
import Darwin
#endif

// MARK: - SourceKitObjectConvertible

public protocol SourceKitObjectConvertible {
Expand Down Expand Up @@ -136,7 +144,7 @@ extension SourceKitObject: SourceKitObjectConvertible {
extension SourceKitObject: CustomStringConvertible {
public var description: String {
let bytes = sourcekitd_request_description_copy(sourcekitdObject)!
defer { bytes.deallocate() }
defer { free(bytes) }
return String(cString: bytes)
}
}
Expand Down
14 changes: 13 additions & 1 deletion Source/SourceKittenFramework/SwiftDocs.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Foundation

#if SWIFT_PACKAGE
import SourceKit
#endif

#if os(Linux)
import Glibc
#elseif os(Windows)
import CRT
#else
import Darwin
#endif
Expand Down Expand Up @@ -68,6 +72,14 @@ public struct SwiftDocs {
extension SwiftDocs: CustomStringConvertible {
/// A textual JSON representation of `SwiftDocs`.
public var description: String {
return toJSON(toNSDictionary([file.path ?? "<No File>": docsDictionary]))
let source: String
if let path = file.path {
source = URL(fileURLWithPath: path).standardizedFileURL.withUnsafeFileSystemRepresentation {
String(cString: $0!)
}
} else {
source = "<No File>"
}
return toJSON(toNSDictionary([source: docsDictionary]))
}
}
2 changes: 2 additions & 0 deletions Source/SourceKittenFramework/Xcode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ public func sdkPath() -> String {
#if os(Linux)
// xcrun does not exist on Linux
return ""
#elseif os(Windows)
return ProcessInfo.processInfo.environment["SDKROOT"] ?? ""
#else
return Exec.run("/usr/bin/xcrun", "--show-sdk-path", "--sdk", "macosx").string ?? ""
#endif
Expand Down
14 changes: 14 additions & 0 deletions Source/SourceKittenFramework/library_wrapper_Clang_C.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
#if !os(Linux)

#if os(Windows)
import WinSDK
#else
import Darwin
#endif

#if SWIFT_PACKAGE
import Clang_C
#endif

#if os(Windows)
private let library = toolchainLoader.load(path: "libclang.dll")
#else
private let library = toolchainLoader.load(path: "libclang.dylib")
#endif

// swiftlint:disable unused_declaration - We don't care if some of these are unused.

internal let clang_getCString: @convention(c) (CXString) -> (UnsafePointer<CChar>?) = library.load(symbol: "clang_getCString")
internal let clang_disposeString: @convention(c) (CXString) -> () = library.load(symbol: "clang_disposeString")
internal let clang_disposeStringSet: @convention(c) (UnsafeMutablePointer<CXStringSet>?) -> () = library.load(symbol: "clang_disposeStringSet")
Expand Down Expand Up @@ -386,4 +399,5 @@ internal let clang_VerbatimLineComment_getText: @convention(c) (CXComment) -> (C
internal let clang_HTMLTagComment_getAsString: @convention(c) (CXComment) -> (CXString) = library.load(symbol: "clang_HTMLTagComment_getAsString")
internal let clang_FullComment_getAsHTML: @convention(c) (CXComment) -> (CXString) = library.load(symbol: "clang_FullComment_getAsHTML")
internal let clang_FullComment_getAsXML: @convention(c) (CXComment) -> (CXString) = library.load(symbol: "clang_FullComment_getAsXML")

#endif
10 changes: 7 additions & 3 deletions Source/SourceKittenFramework/library_wrapper_SourceKit.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#if SWIFT_PACKAGE
import SourceKit
#endif

#if os(Linux)
private let path = "libsourcekitdInProc.so"
private let library = toolchainLoader.load(path: "libsourcekitdInProc.so")
#elseif os(Windows)
private let library = toolchainLoader.load(path: "sourcekitdInProc.dll")
#else
private let path = "sourcekitdInProc.framework/Versions/A/sourcekitdInProc"
private let library = toolchainLoader.load(path: "sourcekitdInProc.framework/Versions/A/sourcekitdInProc")
#endif
private let library = toolchainLoader.load(path: path)

// swiftlint:disable unused_declaration - We don't care if some of these are unused.

internal let sourcekitd_initialize: @convention(c) () -> () = library.load(symbol: "sourcekitd_initialize")
internal let sourcekitd_shutdown: @convention(c) () -> () = library.load(symbol: "sourcekitd_shutdown")
internal let sourcekitd_set_interrupted_connection_handler: @convention(c) (@escaping sourcekitd_interrupted_connection_handler_t) -> () = library.load(symbol: "sourcekitd_set_interrupted_connection_handler")
Expand Down
2 changes: 2 additions & 0 deletions Source/sourcekitten/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import ArgumentParser
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif os(Windows)
import ucrt
#else
#error("Unsupported platform")
#endif
Expand Down
Loading