mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
133 lines
5.5 KiB
Swift
133 lines
5.5 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2024 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
package import BuildServerProtocol
|
|
import BuildSystemIntegration
|
|
import Foundation
|
|
import LanguageServerProtocol
|
|
import LanguageServerProtocolExtensions
|
|
import SKLogging
|
|
import SwiftExtensions
|
|
|
|
import struct TSCBasic.AbsolutePath
|
|
import class TSCBasic.Process
|
|
|
|
private let preparationIDForLogging = AtomicUInt32(initialValue: 1)
|
|
|
|
/// Describes a task to prepare a set of targets.
|
|
///
|
|
/// This task description can be scheduled in a `TaskScheduler`.
|
|
package struct PreparationTaskDescription: IndexTaskDescription {
|
|
package static let idPrefix = "prepare"
|
|
|
|
package let id = preparationIDForLogging.fetchAndIncrement()
|
|
|
|
/// The targets that should be prepared.
|
|
package let targetsToPrepare: [BuildTargetIdentifier]
|
|
|
|
/// The build system manager that is used to get the toolchain and build settings for the files to index.
|
|
private let buildSystemManager: BuildSystemManager
|
|
|
|
private let preparationUpToDateTracker: UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>
|
|
|
|
/// See `SemanticIndexManager.logMessageToIndexLog`.
|
|
private let logMessageToIndexLog:
|
|
@Sendable (_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind) -> Void
|
|
|
|
/// Hooks that should be called when the preparation task finishes.
|
|
private let hooks: IndexHooks
|
|
|
|
/// The task is idempotent because preparing the same target twice produces the same result as preparing it once.
|
|
package var isIdempotent: Bool { true }
|
|
|
|
package var estimatedCPUCoreCount: Int { 1 }
|
|
|
|
package var description: String {
|
|
return self.redactedDescription
|
|
}
|
|
|
|
package var redactedDescription: String {
|
|
return "preparation-\(id)"
|
|
}
|
|
|
|
init(
|
|
targetsToPrepare: [BuildTargetIdentifier],
|
|
buildSystemManager: BuildSystemManager,
|
|
preparationUpToDateTracker: UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>,
|
|
logMessageToIndexLog:
|
|
@escaping @Sendable (
|
|
_ message: String, _ type: WindowMessageType, _ structure: StructuredLogKind
|
|
) -> Void,
|
|
hooks: IndexHooks
|
|
) {
|
|
self.targetsToPrepare = targetsToPrepare
|
|
self.buildSystemManager = buildSystemManager
|
|
self.preparationUpToDateTracker = preparationUpToDateTracker
|
|
self.logMessageToIndexLog = logMessageToIndexLog
|
|
self.hooks = hooks
|
|
}
|
|
|
|
package func execute() async {
|
|
// Only use the last two digits of the preparation ID for the logging scope to avoid creating too many scopes.
|
|
// See comment in `withLoggingScope`.
|
|
// The last 2 digits should be sufficient to differentiate between multiple concurrently running preparation operations
|
|
await withLoggingSubsystemAndScope(subsystem: indexLoggingSubsystem, scope: "preparation-\(id % 100)") {
|
|
let targetsToPrepare = await targetsToPrepare.asyncFilter { await !preparationUpToDateTracker.isUpToDate($0) }
|
|
// Sort targets to get deterministic ordering. The actual order does not matter.
|
|
.sorted { $0.uri.stringValue < $1.uri.stringValue }
|
|
if targetsToPrepare.isEmpty {
|
|
return
|
|
}
|
|
await hooks.preparationTaskDidStart?(self)
|
|
|
|
let targetsToPrepareDescription = targetsToPrepare.map(\.uri.stringValue).joined(separator: ", ")
|
|
logger.log(
|
|
"Starting preparation with priority \(Task.currentPriority.rawValue, privacy: .public): \(targetsToPrepareDescription)"
|
|
)
|
|
let signposter = Logger(subsystem: LoggingScope.subsystem, category: "preparation").makeSignposter()
|
|
let signpostID = signposter.makeSignpostID()
|
|
let state = signposter.beginInterval("Preparing", id: signpostID, "Preparing \(targetsToPrepareDescription)")
|
|
let startDate = Date()
|
|
defer {
|
|
logger.log(
|
|
"Finished preparation in \(Date().timeIntervalSince(startDate) * 1000, privacy: .public)ms: \(targetsToPrepareDescription)"
|
|
)
|
|
signposter.endInterval("Preparing", state)
|
|
}
|
|
do {
|
|
try await buildSystemManager.prepare(targets: Set(targetsToPrepare))
|
|
} catch {
|
|
logger.error("Preparation failed: \(error.forLogging)")
|
|
}
|
|
await hooks.preparationTaskDidFinish?(self)
|
|
if !Task.isCancelled {
|
|
await preparationUpToDateTracker.markUpToDate(targetsToPrepare, updateOperationStartDate: startDate)
|
|
}
|
|
}
|
|
}
|
|
|
|
package func dependencies(
|
|
to currentlyExecutingTasks: [PreparationTaskDescription]
|
|
) -> [TaskDependencyAction<PreparationTaskDescription>] {
|
|
return currentlyExecutingTasks.compactMap { (other) -> TaskDependencyAction<PreparationTaskDescription>? in
|
|
if other.targetsToPrepare.count > self.targetsToPrepare.count {
|
|
// If there is an prepare operation with more targets already running, suspend it.
|
|
// The most common use case for this is if we prepare all targets simultaneously during the initial preparation
|
|
// when a project is opened and need a single target indexed for user interaction. We should suspend the
|
|
// workspace-wide preparation and just prepare the currently needed target.
|
|
return .cancelAndRescheduleDependency(other)
|
|
}
|
|
return .waitAndElevatePriorityOfDependency(other)
|
|
}
|
|
}
|
|
}
|