-
Notifications
You must be signed in to change notification settings - Fork 379
Expand file tree
/
Copy pathPreparationTaskDescription.swift
More file actions
137 lines (118 loc) · 5.56 KB
/
Copy pathPreparationTaskDescription.swift
File metadata and controls
137 lines (118 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import BuildServerIntegration
@_spi(SourceKitLSP) package import BuildServerProtocol
import Foundation
@_spi(SourceKitLSP) import LanguageServerProtocol
@_spi(SourceKitLSP) import LanguageServerProtocolExtensions
@_spi(SourceKitLSP) import SKLogging
import SwiftExtensions
@_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions
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 server manager that is used to get the toolchain and build settings for the files to index.
private let buildServerManager: BuildServerManager
private let preparationUpToDateTracker: UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>
/// See `SemanticIndexManager.logMessageToIndexLog`.
private let logMessageToIndexLog:
@Sendable (
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
) -> Void
/// Hooks that should be called when the preparation task finishes.
private let hooks: IndexHooks
private let purpose: TargetPreparationPurpose
/// 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],
buildServerManager: BuildServerManager,
preparationUpToDateTracker: UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>,
logMessageToIndexLog:
@escaping @Sendable (
_ message: String, _ type: WindowMessageType, _ structure: LanguageServerProtocol.StructuredLogKind
) -> Void,
hooks: IndexHooks,
purpose: TargetPreparationPurpose
) {
self.targetsToPrepare = targetsToPrepare
self.buildServerManager = buildServerManager
self.preparationUpToDateTracker = preparationUpToDateTracker
self.logMessageToIndexLog = logMessageToIndexLog
self.hooks = hooks
self.purpose = purpose
}
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 buildServerManager.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.purpose == .forIndexing && self.purpose == .forEditorFunctionality {
// If we're running a background indexing operation but need a target indexed for user interaction,
// we should prioritize the latter.
return .cancelAndRescheduleDependency(other)
}
return .waitAndElevatePriorityOfDependency(other)
}
}
}