Skip to content

Commit

Permalink
Merge pull request #178 from jpsim/nn-swift-test
Browse files Browse the repository at this point in the history
Runtime linking of Clang & SourceKit, codegen & update to latest Swift snapshot
  • Loading branch information
jpsim committed Mar 17, 2016
2 parents 0010e0c + 48a2fff commit ae1740f
Show file tree
Hide file tree
Showing 69 changed files with 646 additions and 205 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ matrix:
- script: make spm_test
env: JOB=SPM
before_install:
- make bootstrap
- make swift_snapshot_install
- make spm_bootstrap
exclude:
- script: placeholder # workaround for https://github.com/travis-ci/travis-ci/issues/4681
notifications:
Expand Down
17 changes: 14 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@

##### Breaking

* None.
* Now `libclang.dylib` and `sourcekitd.framework` are dynamically loaded at
runtime by SourceKittenFramework to use the versions included in the Xcode
version specified by `xcode-select -p` or custom toolchains. If
SourceKittenFramework clients previously accessed either of these libraries
directly using their APIs, those are no longer available.
[Norio Nomura](https://github.com/norio-nomura)
[#167](https://github.com/jpsim/SourceKitten/issues/167)

##### Enhancements

* None.
* Simplify the process of generating library wrappers and validate library
wrappers in unit tests.
[JP Simard](https://github.com/jpsim)

* Support `swift test` on OS X.
[Norio Nomura](https://github.com/norio-nomura)

##### Bug Fixes

Expand Down Expand Up @@ -45,7 +56,7 @@

* Fix crash when offset points end of string.
[Norio Nomura](https://github.com/norio-nomura)
[#164](https://github.com/realm/SwiftLint/issues/164)
[SwiftLint#164](https://github.com/realm/SwiftLint/issues/164)

## 0.9.0

Expand Down
17 changes: 7 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ OUTPUT_PACKAGE=SourceKitten.pkg
VERSION_STRING=$(shell agvtool what-marketing-version -terse1)
COMPONENTS_PLIST=Source/sourcekitten/Components.plist

SWIFT_SNAPSHOT=swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a
SWIFT_BUILD_COMMAND=/Library/Developer/Toolchains/$(SWIFT_SNAPSHOT).xctoolchain/usr/bin/swift build
SWIFT_SNAPSHOT=swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a
SWIFT_COMMAND=/Library/Developer/Toolchains/$(SWIFT_SNAPSHOT).xctoolchain/usr/bin/swift
SWIFT_BUILD_COMMAND=$(SWIFT_COMMAND) build
SWIFT_TEST_COMMAND=$(SWIFT_COMMAND) test

.PHONY: all bootstrap clean install package test uninstall

Expand Down Expand Up @@ -74,17 +76,12 @@ swift_snapshot_install:
curl https://swift.org/builds/development/xcode/$(SWIFT_SNAPSHOT)/$(SWIFT_SNAPSHOT)-osx.pkg -o swift.pkg
sudo installer -pkg swift.pkg -target /

spm_bootstrap: spm_teardown
script/spm_bootstrap "$(SWIFT_SNAPSHOT)"

spm_teardown:
script/spm_teardown

spm:
$(SWIFT_BUILD_COMMAND)
$(SWIFT_BUILD_COMMAND) -v

spm_test: PATH:=/Library/Developer/Toolchains/$(SWIFT_SNAPSHOT).xctoolchain/usr/bin/:$(PATH)
spm_test: spm
.build/Debug/SourceKittenFrameworkTests
$(SWIFT_TEST_COMMAND)

spm_clean:
$(SWIFT_BUILD_COMMAND) --clean
Expand Down
6 changes: 2 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ let package = Package(
Target(name: "SourceKittenFramework"),
Target(name: "sourcekitten",
dependencies: [.Target(name: "SourceKittenFramework")]),
Target(name: "SourceKittenFrameworkTests",
dependencies: [.Target(name: "SourceKittenFramework")]),
],
dependencies: [
.Package(url: "https://github.com/antitypical/Result.git", majorVersion: 1),
.Package(url: "https://github.com/jpsim/SourceKit.git", majorVersion: 1),
.Package(url: "https://github.com/norio-nomura/SourceKit.git", majorVersion: 1),
.Package(url: "https://github.com/norio-nomura/Clang_C.git", majorVersion: 1),
.Package(url: "https://github.com/drmohundro/SWXMLHash.git", majorVersion: 2),
.Package(url: "https://github.com/Carthage/Commandant.git", majorVersion: 0, minor: 8),
.Package(url: "https://github.com/norio-nomura/swift-corelibs-xctest.git", majorVersion: 0),
]
)
49 changes: 49 additions & 0 deletions Source/SourceKittenFramework/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,52 @@ extension Request: CustomStringConvertible {
/// A textual representation of `Request`.
public var description: String { return String(UTF8String: sourcekitd_request_description_copy(sourcekitObject))! }
}

private func interfaceForModule(module: String, compilerArguments: [String]) -> [String: SourceKitRepresentable] {
var compilerargs = compilerArguments.map({ sourcekitd_request_string_create($0) })
let dict = [
sourcekitd_uid_get_from_cstr("key.request"): sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.open.interface")),
sourcekitd_uid_get_from_cstr("key.name"): sourcekitd_request_string_create(NSUUID().UUIDString),
sourcekitd_uid_get_from_cstr("key.compilerargs"): sourcekitd_request_array_create(&compilerargs, compilerargs.count),
sourcekitd_uid_get_from_cstr("key.modulename"): sourcekitd_request_string_create("SourceKittenFramework.\(module)")
]
var keys = Array(dict.keys)
var values = Array(dict.values)
return Request.CustomRequest(sourcekitd_request_dictionary_create(&keys, &values, dict.count)).send()
}

internal func libraryWrapperForModule(module: String, loadPath: String, spmModule: String, compilerArguments: [String]) -> String {
let sourceKitResponse = interfaceForModule(module, compilerArguments: compilerArguments)
let substructure = SwiftDocKey.getSubstructure(Structure(sourceKitResponse: sourceKitResponse).dictionary)!.map({ $0 as! [String: SourceKitRepresentable] })
let source = sourceKitResponse["key.sourcetext"] as! String
let freeFunctions = substructure.filter({
SwiftDeclarationKind(rawValue: SwiftDocKey.getKind($0)!) == .FunctionFree
}).flatMap { function -> String? in
let fullFunctionName = function["key.name"] as! String
let name = fullFunctionName.substringToIndex(fullFunctionName.rangeOfString("(")!.startIndex)
guard name != "clang_executeOnThread" else { // unsupported format
return nil
}

var parameters = [String]()
if let functionSubstructure = SwiftDocKey.getSubstructure(function) {
for parameterStructure in functionSubstructure {
parameters.append((parameterStructure as! [String: SourceKitRepresentable])["key.typename"] as! String)
}
}
var returnTypes = [String]()
if let offset = SwiftDocKey.getOffset(function), length = SwiftDocKey.getLength(function) {
let start = source.startIndex.advancedBy(Int(offset))
let end = start.advancedBy(Int(length))
let functionDeclaration = source.substringWithRange(start..<end)
if let startOfReturnArrow = functionDeclaration.rangeOfString("->", options: NSStringCompareOptions.BackwardsSearch, range: nil, locale: nil)?.startIndex {
returnTypes.append(functionDeclaration.substringFromIndex(startOfReturnArrow.advancedBy(3)))
}
}

return "internal let \(name): @convention(c) (\(parameters.joinWithSeparator(", "))) -> (\(returnTypes.joinWithSeparator(", "))) = library.loadSymbol(\"\(name)\")"
}
let spmImport = "#if SWIFT_PACKAGE\nimport \(spmModule)\n#endif\n"
let library = "private let library = toolchainLoader.load(\"\(loadPath)\")\n"
return spmImport + library + freeFunctions.joinWithSeparator("\n") + "\n"
}
140 changes: 140 additions & 0 deletions Source/SourceKittenFramework/library_wrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// library_wrapper.swift
// sourcekitten
//
// Created by Norio Nomura on 2/20/16.
// Copyright © 2016 SourceKitten. All rights reserved.
//

import Foundation

struct DynamicLinkLibrary {
let path: String
let handle: UnsafeMutablePointer<Void>

func loadSymbol<T>(symbol: String) -> T {
let sym = Darwin.dlsym(handle, symbol)
if sym == nil {
let errorString = String(UTF8String: dlerror())
fatalError("Finding symbol \(symbol) failed: \(errorString)")
}
return unsafeBitCast(sym, T.self)
}
}

let toolchainLoader = Loader(searchPaths: [
xcodeDefaultToolchainOverride,
toolchainDir,
xcodeSelectPath?.toolchainDir,
/*
These search paths are used when `xcode-select -p` points to
"Command Line Tools OS X for Xcode", but Xcode.app exists.
*/
applicationsDir?.xcodeDeveloperDir.toolchainDir,
applicationsDir?.xcodeBetaDeveloperDir.toolchainDir,
userApplicationsDir?.xcodeDeveloperDir.toolchainDir,
userApplicationsDir?.xcodeBetaDeveloperDir.toolchainDir,
].flatMap { path in
if let fullPath = path?.usrLibDir where fullPath.isFile {
return fullPath
}
return nil
})

struct Loader {
let searchPaths: [String]

func load(path: String) -> DynamicLinkLibrary {
let fullPaths = searchPaths.map { $0.stringByAppendingPathComponent(path) }.filter { $0.isFile }

// try all fullPaths that contains target file,
// then try loading with simple path that depends resolving to DYLD
for fullPath in fullPaths + [path] {
let handle = dlopen(fullPath, RTLD_LAZY)
if handle != nil {
return DynamicLinkLibrary(path: path, handle: handle)
}
}

fatalError("Loading \(path) failed")
}
}

/// Returns "XCODE_DEFAULT_TOOLCHAIN_OVERRIDE" environment variable
///
/// `launch-with-toolchain` sets the toolchain path to the
/// "XCODE_DEFAULT_TOOLCHAIN_OVERRIDE" environment variable.
private let xcodeDefaultToolchainOverride: String? =
NSProcessInfo.processInfo().environment["XCODE_DEFAULT_TOOLCHAIN_OVERRIDE"]

/// Returns "TOOLCHAIN_DIR" environment variable
///
/// `Xcode`/`xcodebuild` sets the toolchain path to the
/// "TOOLCHAIN_DIR" environment variable.
private let toolchainDir: String? =
NSProcessInfo.processInfo().environment["TOOLCHAIN_DIR"]

/// Returns result string of `xcode-select -p`
private let xcodeSelectPath: String? = {
let pathOfXcodeSelect = "/usr/bin/xcode-select"

if !NSFileManager.defaultManager().isExecutableFileAtPath(pathOfXcodeSelect) {
return nil
}

let task = NSTask()
task.launchPath = pathOfXcodeSelect
task.arguments = ["-p"]

let pipe = NSPipe()
task.standardOutput = pipe
task.launch() // if xcode-select does not exist, crash with `NSInvalidArgumentException`.

let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: NSUTF8StringEncoding) else {
return nil
}

var start = output.startIndex
var contentsEnd = output.startIndex
output.getLineStart(&start, end: nil, contentsEnd: &contentsEnd, forRange: start..<start)
let xcodeSelectPath = output.substringWithRange(start..<contentsEnd)
// Return nil if xcodeSelectPath points to "Command Line Tools OS X for Xcode"
// because it doesn't contain `sourcekitd.framework`.
if xcodeSelectPath == "/Library/Developer/CommandLineTools" {
return nil
}
return xcodeSelectPath
}()

private let applicationsDir: String? =
NSSearchPathForDirectoriesInDomains(.ApplicationDirectory, .SystemDomainMask, true).first

private let userApplicationsDir: String? =
NSSearchPathForDirectoriesInDomains(.ApplicationDirectory, .UserDomainMask, true).first

private extension String {
private var isFile: Bool {
return NSFileManager.defaultManager().fileExistsAtPath(self)
}

private var toolchainDir: String {
return stringByAppendingPathComponent("Toolchains/XcodeDefault.xctoolchain")
}

private var xcodeDeveloperDir: String {
return stringByAppendingPathComponent("Xcode.app/Contents/Developer")
}

private var xcodeBetaDeveloperDir: String {
return stringByAppendingPathComponent("Xcode-beta.app/Contents/Developer")
}

private var usrLibDir: String {
return stringByAppendingPathComponent("/usr/lib")
}

private func stringByAppendingPathComponent(str: String) -> String {
return (self as NSString).stringByAppendingPathComponent(str)
}
}
6 changes: 6 additions & 0 deletions Source/SourceKittenFramework/library_wrapper_CXString.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#if SWIFT_PACKAGE
import Clang_C
#endif
private let library = toolchainLoader.load("libclang.dylib")
internal let clang_getCString: @convention(c) (CXString) -> (UnsafePointer<Int8>) = library.loadSymbol("clang_getCString")
internal let clang_disposeString: @convention(c) (CXString) -> () = library.loadSymbol("clang_disposeString")
38 changes: 38 additions & 0 deletions Source/SourceKittenFramework/library_wrapper_Documentation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#if SWIFT_PACKAGE
import Clang_C
#endif
private let library = toolchainLoader.load("libclang.dylib")
internal let clang_Cursor_getParsedComment: @convention(c) (CXCursor) -> (CXComment) = library.loadSymbol("clang_Cursor_getParsedComment")
internal let clang_Comment_getKind: @convention(c) (CXComment) -> (CXCommentKind) = library.loadSymbol("clang_Comment_getKind")
internal let clang_Comment_getNumChildren: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_Comment_getNumChildren")
internal let clang_Comment_getChild: @convention(c) (CXComment, UInt32) -> (CXComment) = library.loadSymbol("clang_Comment_getChild")
internal let clang_Comment_isWhitespace: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_Comment_isWhitespace")
internal let clang_InlineContentComment_hasTrailingNewline: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_InlineContentComment_hasTrailingNewline")
internal let clang_TextComment_getText: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_TextComment_getText")
internal let clang_InlineCommandComment_getCommandName: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_InlineCommandComment_getCommandName")
internal let clang_InlineCommandComment_getRenderKind: @convention(c) (CXComment) -> (CXCommentInlineCommandRenderKind) = library.loadSymbol("clang_InlineCommandComment_getRenderKind")
internal let clang_InlineCommandComment_getNumArgs: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_InlineCommandComment_getNumArgs")
internal let clang_InlineCommandComment_getArgText: @convention(c) (CXComment, UInt32) -> (CXString) = library.loadSymbol("clang_InlineCommandComment_getArgText")
internal let clang_HTMLTagComment_getTagName: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_HTMLTagComment_getTagName")
internal let clang_HTMLStartTagComment_isSelfClosing: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_HTMLStartTagComment_isSelfClosing")
internal let clang_HTMLStartTag_getNumAttrs: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_HTMLStartTag_getNumAttrs")
internal let clang_HTMLStartTag_getAttrName: @convention(c) (CXComment, UInt32) -> (CXString) = library.loadSymbol("clang_HTMLStartTag_getAttrName")
internal let clang_HTMLStartTag_getAttrValue: @convention(c) (CXComment, UInt32) -> (CXString) = library.loadSymbol("clang_HTMLStartTag_getAttrValue")
internal let clang_BlockCommandComment_getCommandName: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_BlockCommandComment_getCommandName")
internal let clang_BlockCommandComment_getNumArgs: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_BlockCommandComment_getNumArgs")
internal let clang_BlockCommandComment_getArgText: @convention(c) (CXComment, UInt32) -> (CXString) = library.loadSymbol("clang_BlockCommandComment_getArgText")
internal let clang_BlockCommandComment_getParagraph: @convention(c) (CXComment) -> (CXComment) = library.loadSymbol("clang_BlockCommandComment_getParagraph")
internal let clang_ParamCommandComment_getParamName: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_ParamCommandComment_getParamName")
internal let clang_ParamCommandComment_isParamIndexValid: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_ParamCommandComment_isParamIndexValid")
internal let clang_ParamCommandComment_getParamIndex: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_ParamCommandComment_getParamIndex")
internal let clang_ParamCommandComment_isDirectionExplicit: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_ParamCommandComment_isDirectionExplicit")
internal let clang_ParamCommandComment_getDirection: @convention(c) (CXComment) -> (CXCommentParamPassDirection) = library.loadSymbol("clang_ParamCommandComment_getDirection")
internal let clang_TParamCommandComment_getParamName: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_TParamCommandComment_getParamName")
internal let clang_TParamCommandComment_isParamPositionValid: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_TParamCommandComment_isParamPositionValid")
internal let clang_TParamCommandComment_getDepth: @convention(c) (CXComment) -> (UInt32) = library.loadSymbol("clang_TParamCommandComment_getDepth")
internal let clang_TParamCommandComment_getIndex: @convention(c) (CXComment, UInt32) -> (UInt32) = library.loadSymbol("clang_TParamCommandComment_getIndex")
internal let clang_VerbatimBlockLineComment_getText: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_VerbatimBlockLineComment_getText")
internal let clang_VerbatimLineComment_getText: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_VerbatimLineComment_getText")
internal let clang_HTMLTagComment_getAsString: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_HTMLTagComment_getAsString")
internal let clang_FullComment_getAsHTML: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_FullComment_getAsHTML")
internal let clang_FullComment_getAsXML: @convention(c) (CXComment) -> (CXString) = library.loadSymbol("clang_FullComment_getAsXML")
Loading

0 comments on commit ae1740f

Please sign in to comment.