mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
The JSON compilation database allows specifying arguments either as an
array of string ("arguments") or as a single shell-escaped string
("command"). When using "command", we were hitting very slow load times
for large compilation databases. This commit speeds up splitting the
shell-escaped command by not iterating the UTF-8 view instead of the
graphemes.
In release builds I see ~10x speedup of the splitting perf test, and
~3-5x in a debug build. In a real-world test using the cmake-generated
compile_commands.json for llvm+clang this sped up overall compilation
database loading from 11 seconds to 1.2 seconds on my machine.
315 lines
9.5 KiB
Swift
315 lines
9.5 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import LanguageServerProtocol
|
|
import LSPTestSupport
|
|
import SKCore
|
|
import TSCBasic
|
|
import XCTest
|
|
|
|
final class CompilationDatabaseTests: XCTestCase {
|
|
func testSplitShellEscapedCommand() {
|
|
func check(_ str: String, _ expected: [String], file: StaticString=#file, line: UInt=#line) {
|
|
XCTAssertEqual(splitShellEscapedCommand(str), expected, file: file, line: line)
|
|
}
|
|
|
|
check("", [])
|
|
check(" ", [])
|
|
check("a", ["a"])
|
|
check("abc", ["abc"])
|
|
check("a😀c", ["a😀c"])
|
|
check("😀c", ["😀c"])
|
|
check("abc def", ["abc", "def"])
|
|
check("abc def", ["abc", "def"])
|
|
|
|
check("\"", [""])
|
|
check("\"a", ["a"])
|
|
check("\"\"", [""])
|
|
check("\"a\"", ["a"])
|
|
check("\"a\\\"\"", ["a\""])
|
|
check("\"a b c \"", ["a b c "])
|
|
check("\"a \" ", ["a "])
|
|
check("\"a \" b", ["a ", "b"])
|
|
check("\"a \"b", ["a b"])
|
|
check("a\"x \"\"b", ["ax b"])
|
|
|
|
check("\'", [""])
|
|
check("\'a", ["a"])
|
|
check("\'\'", [""])
|
|
check("\'a\'", ["a"])
|
|
check("\'a\\\"\'", ["a\\\""])
|
|
check("\'a b c \'", ["a b c "])
|
|
check("\'a \' ", ["a "])
|
|
check("\'a \' b", ["a ", "b"])
|
|
check("\'a \'b", ["a b"])
|
|
check("a\'x \'\'b", ["ax b"])
|
|
|
|
check("a\\\\", ["a\\"])
|
|
check("\"a\"bcd\"ef\"\"\"\"g\"", ["abcdefg"])
|
|
check("a'\\b \"c\"'", ["a\\b \"c\""])
|
|
}
|
|
|
|
func testEncodeCompDBCommand() {
|
|
// Requires JSONEncoder.OutputFormatting.sortedKeys
|
|
if #available(macOS 10.13, *) {
|
|
func check(_ cmd: CompilationDatabase.Command, _ expected: String, file: StaticString = #file, line: UInt = #line) {
|
|
let encoder = JSONEncoder()
|
|
encoder.outputFormatting.insert(.sortedKeys)
|
|
let encodedString = try! String(data: encoder.encode(cmd), encoding: .utf8)
|
|
XCTAssertEqual(encodedString, expected, file: file, line: line)
|
|
}
|
|
|
|
check(.init(directory: "a", filename: "b", commandLine: [], output: "c"), """
|
|
{"arguments":[],"directory":"a","file":"b","output":"c"}
|
|
""")
|
|
check(.init(directory: "a", filename: "b", commandLine: ["c", "d"], output: nil), """
|
|
{"arguments":["c","d"],"directory":"a","file":"b"}
|
|
""")
|
|
}
|
|
}
|
|
|
|
func testDecodeCompDBCommand() {
|
|
func check(_ str: String, _ expected: CompilationDatabase.Command, file: StaticString = #file, line: UInt = #line) {
|
|
let cmd = try! JSONDecoder().decode(CompilationDatabase.Command.self, from: str.data(using: .utf8)!)
|
|
XCTAssertEqual(cmd, expected, file: file, line: line)
|
|
}
|
|
|
|
check("""
|
|
{
|
|
"arguments" : [
|
|
|
|
],
|
|
"directory" : "a",
|
|
"file" : "b",
|
|
"output" : "c"
|
|
}
|
|
""", .init(directory: "a", filename: "b", commandLine: [], output: "c"))
|
|
check("""
|
|
{
|
|
"arguments" : [
|
|
"c",
|
|
"d"
|
|
],
|
|
"directory" : "a",
|
|
"file" : "b"
|
|
}
|
|
""", .init(directory: "a", filename: "b", commandLine: ["c", "d"], output: nil))
|
|
|
|
check("""
|
|
{
|
|
"directory":"a",
|
|
"file":"b.cpp",
|
|
"command": "/usr/bin/clang++ -std=c++11 -DFOO b.cpp"
|
|
}
|
|
""", .init(
|
|
directory: "a",
|
|
filename: "b.cpp",
|
|
commandLine: [
|
|
"/usr/bin/clang++",
|
|
"-std=c++11",
|
|
"-DFOO",
|
|
"b.cpp",
|
|
],
|
|
output: nil
|
|
))
|
|
|
|
XCTAssertThrowsError(try JSONDecoder().decode(CompilationDatabase.Command.self, from: """
|
|
{"directory":"a","file":"b"}
|
|
""".data(using: .utf8)!))
|
|
}
|
|
|
|
func testJSONCompilationDatabaseCoding() {
|
|
checkCoding(JSONCompilationDatabase([]), json: """
|
|
[
|
|
|
|
]
|
|
""")
|
|
let db = JSONCompilationDatabase([
|
|
.init(directory: "a", filename: "b", commandLine: [], output: nil),
|
|
.init(directory: "c", filename: "b", commandLine: [], output: nil),
|
|
])
|
|
checkCoding(db, json: """
|
|
[
|
|
{
|
|
"arguments" : [
|
|
|
|
],
|
|
"directory" : "a",
|
|
"file" : "b"
|
|
},
|
|
{
|
|
"arguments" : [
|
|
|
|
],
|
|
"directory" : "c",
|
|
"file" : "b"
|
|
}
|
|
]
|
|
""")
|
|
}
|
|
|
|
func testJSONCompilationDatabaseLookup() {
|
|
let cmd1 = CompilationDatabase.Command(directory: "a", filename: "b", commandLine: [], output: nil)
|
|
let cmd2 = CompilationDatabase.Command(directory: "/c", filename: "b", commandLine: [], output: nil)
|
|
let cmd3 = CompilationDatabase.Command(directory: "/c", filename: "/b", commandLine: [], output: nil)
|
|
|
|
let db = JSONCompilationDatabase([cmd1, cmd2, cmd3])
|
|
|
|
XCTAssertEqual(db[URL(fileURLWithPath: "b")], [cmd1])
|
|
XCTAssertEqual(db[URL(fileURLWithPath: "/c/b")], [cmd2])
|
|
XCTAssertEqual(db[URL(fileURLWithPath: "/b")], [cmd3])
|
|
}
|
|
|
|
func testJSONCompilationDatabaseFromDirectory() {
|
|
let fs = InMemoryFileSystem()
|
|
try! fs.createDirectory(AbsolutePath("/a"))
|
|
XCTAssertNil(tryLoadCompilationDatabase(directory: AbsolutePath("/a"), fs))
|
|
|
|
try! fs.writeFileContents(AbsolutePath("/a/compile_commands.json"), bytes: """
|
|
[
|
|
{
|
|
"file": "/a/a.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "/a/a.swift"]
|
|
}
|
|
]
|
|
""")
|
|
|
|
XCTAssertNotNil(tryLoadCompilationDatabase(directory: AbsolutePath("/a"), fs))
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystem() {
|
|
checkCompilationDatabaseBuildSystem("""
|
|
[
|
|
{
|
|
"file": "/a/a.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-swift-version", "4", "/a/a.swift"]
|
|
}
|
|
]
|
|
""") { buildSystem in
|
|
let settings = buildSystem.settings(for: DocumentURI(URL(fileURLWithPath: "/a/a.swift")), .swift)
|
|
XCTAssertNotNil(settings)
|
|
XCTAssertEqual(settings?.workingDirectory, "/a")
|
|
XCTAssertEqual(settings?.compilerArguments, ["-swift-version", "4", "/a/a.swift"])
|
|
XCTAssertNil(buildSystem.indexStorePath)
|
|
XCTAssertNil(buildSystem.indexDatabasePath)
|
|
}
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystemIndexStoreSwift0() {
|
|
checkCompilationDatabaseBuildSystem("[]") { buildSystem in
|
|
XCTAssertNil(buildSystem.indexStorePath)
|
|
}
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystemIndexStoreSwift1() {
|
|
checkCompilationDatabaseBuildSystem("""
|
|
[
|
|
{
|
|
"file": "/a/a.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-swift-version", "4", "/a/a.swift", "-index-store-path", "/b"]
|
|
}
|
|
]
|
|
""") { buildSystem in
|
|
XCTAssertEqual(buildSystem.indexStorePath, AbsolutePath("/b"))
|
|
XCTAssertEqual(buildSystem.indexDatabasePath, AbsolutePath("/IndexDatabase"))
|
|
}
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystemIndexStoreSwift2() {
|
|
checkCompilationDatabaseBuildSystem("""
|
|
[
|
|
{
|
|
"file": "/a/a.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-swift-version", "4", "/a/a.swift"]
|
|
},
|
|
{
|
|
"file": "/a/b.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-swift-version", "4", "/a/b.swift"]
|
|
},
|
|
{
|
|
"file": "/a/c.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-swift-version", "4", "/a/c.swift", "-index-store-path", "/b"]
|
|
}
|
|
]
|
|
""") { buildSystem in
|
|
XCTAssertEqual(buildSystem.indexStorePath, AbsolutePath("/b"))
|
|
}
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystemIndexStoreSwift3() {
|
|
checkCompilationDatabaseBuildSystem("""
|
|
[
|
|
{
|
|
"file": "/a/a.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-index-store-path", "/b", "-swift-version", "4", "/a/a.swift"]
|
|
}
|
|
]
|
|
""") { buildSystem in
|
|
XCTAssertEqual(buildSystem.indexStorePath, AbsolutePath("/b"))
|
|
}
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystemIndexStoreSwift4() {
|
|
checkCompilationDatabaseBuildSystem("""
|
|
[
|
|
{
|
|
"file": "/a/a.swift",
|
|
"directory": "/a",
|
|
"arguments": ["swiftc", "-swift-version", "4", "/a/c.swift", "-index-store-path"]
|
|
}
|
|
]
|
|
""") { buildSystem in
|
|
XCTAssertNil(buildSystem.indexStorePath)
|
|
}
|
|
}
|
|
|
|
func testCompilationDatabaseBuildSystemIndexStoreClang() {
|
|
checkCompilationDatabaseBuildSystem("""
|
|
[
|
|
{
|
|
"file": "/a/a.cpp",
|
|
"directory": "/a",
|
|
"arguments": ["clang", "/a/a.cpp"]
|
|
},
|
|
{
|
|
"file": "/a/b.cpp",
|
|
"directory": "/a",
|
|
"arguments": ["clang", "/a/b.cpp"]
|
|
},
|
|
{
|
|
"file": "/a/c.cpp",
|
|
"directory": "/a",
|
|
"arguments": ["clang", "/a/c.cpp", "-index-store-path", "/b"]
|
|
}
|
|
]
|
|
""") { buildSystem in
|
|
XCTAssertEqual(buildSystem.indexStorePath, AbsolutePath("/b"))
|
|
XCTAssertEqual(buildSystem.indexDatabasePath, AbsolutePath("/IndexDatabase"))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func checkCompilationDatabaseBuildSystem(_ compdb: ByteString, file: StaticString = #file, line: UInt = #line, block: (BuildSystem) -> ()) {
|
|
let fs = InMemoryFileSystem()
|
|
XCTAssertNoThrow(try fs.createDirectory(AbsolutePath("/a")), file: file, line: line)
|
|
XCTAssertNoThrow(try fs.writeFileContents(AbsolutePath("/a/compile_commands.json"), bytes: compdb), file: file, line: line)
|
|
let buildSystem: BuildSystem = CompilationDatabaseBuildSystem(projectRoot: AbsolutePath("/a"), fileSystem: fs)
|
|
block(buildSystem)
|
|
}
|