Skip to content

Produce an index log for the client #1326

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

Merged
merged 2 commits into from
May 22, 2024
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
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
default.profraw
Package.resolved
/.build
/.index-build
/.linux-build
/.*-build
/Packages
/*.xcodeproj
/*.sublime-project
Expand Down
5 changes: 4 additions & 1 deletion Sources/SKCore/BuildServerBuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,10 @@ extension BuildServerBuildSystem: BuildSystem {
return nil
}

public func prepare(targets: [ConfiguredTarget]) async throws {
public func prepare(
targets: [ConfiguredTarget],
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
) async throws {
throw PrepareNotSupportedError()
}

Expand Down
5 changes: 4 additions & 1 deletion Sources/SKCore/BuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ public protocol BuildSystem: AnyObject, Sendable {

/// Prepare the given targets for indexing and semantic functionality. This should build all swift modules of target
/// dependencies.
func prepare(targets: [ConfiguredTarget]) async throws
func prepare(
targets: [ConfiguredTarget],
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
) async throws

/// If the build system has knowledge about the language that this document should be compiled in, return it.
///
Expand Down
7 changes: 5 additions & 2 deletions Sources/SKCore/BuildSystemManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,11 @@ extension BuildSystemManager {
return await buildSystem?.targets(dependingOn: targets)
}

public func prepare(targets: [ConfiguredTarget]) async throws {
try await buildSystem?.prepare(targets: targets)
public func prepare(
targets: [ConfiguredTarget],
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
) async throws {
try await buildSystem?.prepare(targets: targets, indexProcessDidProduceResult: indexProcessDidProduceResult)
}

public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
Expand Down
1 change: 1 addition & 0 deletions Sources/SKCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_library(SKCore STATIC
Debouncer.swift
FallbackBuildSystem.swift
FileBuildSettings.swift
IndexProcessResult.swift
MainFilesProvider.swift
PathPrefixMapping.swift
SplitShellCommand.swift
Expand Down
5 changes: 4 additions & 1 deletion Sources/SKCore/CompilationDatabaseBuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
return [ConfiguredTarget(targetID: "dummy", runDestinationID: "dummy")]
}

public func prepare(targets: [ConfiguredTarget]) async throws {
public func prepare(
targets: [ConfiguredTarget],
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
) async throws {
throw PrepareNotSupportedError()
}

Expand Down
71 changes: 71 additions & 0 deletions Sources/SKCore/IndexProcessResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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 struct TSCBasic.ProcessResult

/// Result of a process that prepares a target or updates the index store. To be shown in the build log.
///
/// Abstracted over a `ProcessResult` to facilitate build systems that don't spawn a new process to prepare a target but
/// prepare it from a build graph they have loaded in-process.
public struct IndexProcessResult {
/// A human-readable description of what the process was trying to achieve, like `Preparing MyTarget`
public let taskDescription: String

/// The command that was run to produce the result.
public let command: String

/// The output that the process produced.
public let output: String

/// Whether the process failed.
public let failed: Bool

/// The duration it took for the process to execute.
public let duration: Duration

public init(taskDescription: String, command: String, output: String, failed: Bool, duration: Duration) {
self.taskDescription = taskDescription
self.command = command
self.output = output
self.failed = failed
self.duration = duration
}

public init(taskDescription: String, processResult: ProcessResult, start: ContinuousClock.Instant) {
let stdout = (try? String(bytes: processResult.output.get(), encoding: .utf8)) ?? "<failed to decode stdout>"
let stderr = (try? String(bytes: processResult.stderrOutput.get(), encoding: .utf8)) ?? "<failed to decode stderr>"
var outputComponents: [String] = []
if !stdout.isEmpty {
outputComponents.append(
"""
Stdout:
\(stdout)
"""
)
}
if !stderr.isEmpty {
outputComponents.append(
"""
Stderr:
\(stderr)
"""
)
}
self.init(
taskDescription: taskDescription,
command: processResult.arguments.joined(separator: " "),
output: outputComponents.joined(separator: "\n\n"),
failed: processResult.exitStatus != .terminated(code: 0),
duration: start.duration(to: .now)
)
}
}
20 changes: 17 additions & 3 deletions Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -459,17 +459,23 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
}
}

public func prepare(targets: [ConfiguredTarget]) async throws {
public func prepare(
targets: [ConfiguredTarget],
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
) async throws {
// TODO (indexing): Support preparation of multiple targets at once.
// https://github.com/apple/sourcekit-lsp/issues/1262
for target in targets {
try await prepare(singleTarget: target)
try await prepare(singleTarget: target, indexProcessDidProduceResult: indexProcessDidProduceResult)
}
let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] }
await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init)))
}

private func prepare(singleTarget target: ConfiguredTarget) async throws {
private func prepare(
singleTarget target: ConfiguredTarget,
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
) async throws {
// TODO (indexing): Add a proper 'prepare' job in SwiftPM instead of building the target.
// https://github.com/apple/sourcekit-lsp/issues/1254
guard let toolchain = await toolchainRegistry.default else {
Expand All @@ -492,8 +498,16 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
if Task.isCancelled {
return
}
let start = ContinuousClock.now
let process = try Process.launch(arguments: arguments, workingDirectory: nil)
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
indexProcessDidProduceResult(
IndexProcessResult(
taskDescription: "Preparing \(target.targetID) for \(target.runDestinationID)",
processResult: result,
start: start
)
)
switch result.exitStatus.exhaustivelySwitchable {
case .terminated(code: 0):
break
Expand Down
21 changes: 7 additions & 14 deletions Sources/SKTestSupport/TestSourceKitLSPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,25 +243,18 @@ public final class TestSourceKitLSPClient: MessageHandler {
return try await nextNotification(ofType: PublishDiagnosticsNotification.self, timeout: timeout)
}

private struct CastError: Error, CustomStringConvertible {
let expectedType: any NotificationType.Type
let actualType: any NotificationType.Type

var description: String { "Expected a \(expectedType) but got '\(actualType)'" }
}

/// Await the next diagnostic notification sent to the client.
///
/// If the next notification is not of the expected type, this methods throws.
/// Waits for the next notification of the given type to be sent to the client. Ignores any notifications that are of
/// a different type.
public func nextNotification<ExpectedNotificationType: NotificationType>(
ofType: ExpectedNotificationType.Type,
timeout: TimeInterval = defaultTimeout
) async throws -> ExpectedNotificationType {
let nextNotification = try await nextNotification(timeout: timeout)
guard let notification = nextNotification as? ExpectedNotificationType else {
throw CastError(expectedType: ExpectedNotificationType.self, actualType: type(of: nextNotification))
while true {
let nextNotification = try await nextNotification(timeout: timeout)
if let notification = nextNotification as? ExpectedNotificationType {
return notification
}
}
return notification
}

/// Handle the next request that is sent to the client with the given handler.
Expand Down
10 changes: 9 additions & 1 deletion Sources/SemanticIndex/PreparationTaskDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public struct PreparationTaskDescription: IndexTaskDescription {

private let preparationUpToDateStatus: IndexUpToDateStatusManager<ConfiguredTarget>

/// See `SemanticIndexManager.indexProcessDidProduceResult`
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void

/// Test hooks that should be called when the preparation task finishes.
private let testHooks: IndexTestHooks

Expand All @@ -57,11 +60,13 @@ public struct PreparationTaskDescription: IndexTaskDescription {
targetsToPrepare: [ConfiguredTarget],
buildSystemManager: BuildSystemManager,
preparationUpToDateStatus: IndexUpToDateStatusManager<ConfiguredTarget>,
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
testHooks: IndexTestHooks
) {
self.targetsToPrepare = targetsToPrepare
self.buildSystemManager = buildSystemManager
self.preparationUpToDateStatus = preparationUpToDateStatus
self.indexProcessDidProduceResult = indexProcessDidProduceResult
self.testHooks = testHooks
}

Expand Down Expand Up @@ -89,7 +94,10 @@ public struct PreparationTaskDescription: IndexTaskDescription {
)
let startDate = Date()
do {
try await buildSystemManager.prepare(targets: targetsToPrepare)
try await buildSystemManager.prepare(
targets: targetsToPrepare,
indexProcessDidProduceResult: indexProcessDidProduceResult
)
} catch {
logger.error(
"Preparation failed: \(error.forLogging)"
Expand Down
Loading