Skip to content

Commit fc36e07

Browse files
authored
improve output when resolving packages (#3427)
motivation: when resolving packages, the output is lacking key information about computing the appropriate package version. this can mislead users about what SwiftPM is doing and where the time is spent. changes: 1. expose willComputeVersion/didComputeVersion on WorkspaceDelegate 2. use willComputeVersion/didComputeVersion in SwiftTool::ToolWorkspaceDelegate to print information about the version computation stage 3. add duration information to WorkspaceDelegate::fetchingDidFinish and WorkspaceDelegate::repositoryDidUpdate 4. print duration infortmation SwiftTool::ToolWorkspaceDelegate about fetching and updating, in addition to computing 5. remove empty delegates since they are potentialy hiding dperecations 6. adjust test
1 parent b11f273 commit fc36e07

File tree

11 files changed

+233
-111
lines changed

11 files changed

+233
-111
lines changed

Sources/Basics/DispatchTimeInterval+Extensions.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,13 @@ extension DispatchTimeInterval {
5757
}
5858
}
5959
}
60+
61+
// remove when available to all platforms
62+
#if os(Linux) || os(Windows) || os(Android)
63+
extension DispatchTime {
64+
public func distance(to: DispatchTime) -> DispatchTimeInterval {
65+
let duration = to.uptimeNanoseconds - self.uptimeNanoseconds
66+
return .nanoseconds(duration >= Int.max ? Int.max : Int(duration))
67+
}
68+
}
69+
#endif

Sources/Commands/SwiftTool.swift

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import Basics
3030
typealias Diagnostic = TSCBasic.Diagnostic
3131

3232
private class ToolWorkspaceDelegate: WorkspaceDelegate {
33-
3433
/// The stream to use for reporting progress.
3534
private let stdoutStream: ThreadSafeOutputByteStream
3635

@@ -73,7 +72,12 @@ private class ToolWorkspaceDelegate: WorkspaceDelegate {
7372
}
7473
}
7574

76-
func fetchingDidFinish(repository: String, fetchDetails: RepositoryManager.FetchDetails?, diagnostic: Diagnostic?) {
75+
func fetchingDidFinish(repository: String, fetchDetails: RepositoryManager.FetchDetails?, diagnostic: Diagnostic?, duration: DispatchTimeInterval) {
76+
queue.async {
77+
self.stdoutStream <<< "Fetched \(repository) (\(duration.descriptionInSeconds))"
78+
self.stdoutStream <<< "\n"
79+
self.stdoutStream.flush()
80+
}
7781
}
7882

7983
func repositoryWillUpdate(_ repository: String) {
@@ -84,7 +88,12 @@ private class ToolWorkspaceDelegate: WorkspaceDelegate {
8488
}
8589
}
8690

87-
func repositoryDidUpdate(_ repository: String) {
91+
func repositoryDidUpdate(_ repository: String, duration: DispatchTimeInterval) {
92+
queue.async {
93+
self.stdoutStream <<< "Updated \(repository) (\(duration.descriptionInSeconds))"
94+
self.stdoutStream <<< "\n"
95+
self.stdoutStream.flush()
96+
}
8897
}
8998

9099
func dependenciesUpToDate() {
@@ -104,8 +113,15 @@ private class ToolWorkspaceDelegate: WorkspaceDelegate {
104113
}
105114

106115
func willCheckOut(repository: String, revision: String, at path: AbsolutePath) {
116+
// noop
117+
}
118+
119+
func didCheckOut(repository: String, revision: String, at path: AbsolutePath, error: Diagnostic?) {
120+
guard case .none = error else {
121+
return // error will be printed before hand
122+
}
107123
queue.async {
108-
self.stdoutStream <<< "Resolving \(repository) at \(revision)"
124+
self.stdoutStream <<< "Working copy of \(repository) resolved at \(revision)"
109125
self.stdoutStream <<< "\n"
110126
self.stdoutStream.flush()
111127
}
@@ -185,6 +201,22 @@ private class ToolWorkspaceDelegate: WorkspaceDelegate {
185201
}
186202
}
187203

204+
func willComputeVersion(package: PackageIdentity, location: String) {
205+
queue.async {
206+
self.stdoutStream <<< "Computing version for \(location)"
207+
self.stdoutStream <<< "\n"
208+
self.stdoutStream.flush()
209+
}
210+
}
211+
212+
func didComputeVersion(package: PackageIdentity, location: String, version: String, duration: DispatchTimeInterval) {
213+
queue.async {
214+
self.stdoutStream <<< "Computed \(location) at \(version) (\(duration.descriptionInSeconds))"
215+
self.stdoutStream <<< "\n"
216+
self.stdoutStream.flush()
217+
}
218+
}
219+
188220
func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) {
189221
queue.async {
190222
if let totalBytesToDownload = totalBytesToDownload {
@@ -209,6 +241,13 @@ private class ToolWorkspaceDelegate: WorkspaceDelegate {
209241
self.downloadProgress.removeAll()
210242
}
211243
}
244+
245+
// noop
246+
247+
func willLoadManifest(packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) {}
248+
func didLoadManifest(packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind, manifest: Manifest?, diagnostics: [Diagnostic]) {}
249+
func didCreateWorkingCopy(repository url: String, at path: AbsolutePath, error: Diagnostic?) {}
250+
func resolvedFileChanged() {}
212251
}
213252

214253
/// Handler for the main DiagnosticsEngine used by the SwiftTool class.
@@ -888,3 +927,24 @@ extension Diagnostic.Message {
888927
.warning("\(flag) is an *unsupported* option which can be removed at any time; do not rely on it")
889928
}
890929
}
930+
931+
extension DispatchTimeInterval {
932+
var descriptionInSeconds: String {
933+
switch self {
934+
case .seconds(let value):
935+
return "\(value)s"
936+
case .milliseconds(let value):
937+
return String(format: "%.2f", Double(value)/Double(1000)) + "s"
938+
case .microseconds(let value):
939+
return String(format: "%.2f", Double(value)/Double(1_000_000)) + "s"
940+
case .nanoseconds(let value):
941+
return String(format: "%.2f", Double(value)/Double(1_000_000_000)) + "s"
942+
case .never:
943+
return "n/a"
944+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
945+
@unknown default:
946+
return "n/a"
947+
#endif
948+
}
949+
}
950+
}

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11-
import TSCBasic
11+
import Dispatch
1212
import PackageModel
13+
import TSCBasic
1314

1415
public protocol DependencyResolver {
1516
typealias Binding = (package: PackageReference, binding: BoundVersion, products: ProductFilter)
@@ -32,14 +33,14 @@ extension DependencyResolverError: CustomStringConvertible {
3233

3334
public protocol DependencyResolverDelegate {
3435
func willResolve(term: Term)
35-
func didResolve(term: Term, version: Version)
36+
func didResolve(term: Term, version: Version, duration: DispatchTimeInterval)
3637

3738
func derived(term: Term)
3839
func conflict(conflict: Incompatibility)
39-
func satisfied(term: Term, by: Assignment, incompatibility: Incompatibility)
40-
func partiallySatisfied(term: Term, by: Assignment, incompatibility: Incompatibility, difference: Term)
40+
func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility)
41+
func partiallySatisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility, difference: Term)
4142
func failedToResolve(incompatibility: Incompatibility)
42-
func computed(bindings: [DependencyResolver.Binding])
43+
func solved(result: [DependencyResolver.Binding])
4344
}
4445

4546
public struct TracingDependencyResolverDelegate: DependencyResolverDelegate {
@@ -57,7 +58,7 @@ public struct TracingDependencyResolverDelegate: DependencyResolverDelegate {
5758
self.log("resolving: \(term.node.package.location)")
5859
}
5960

60-
public func didResolve(term: Term, version: Version) {
61+
public func didResolve(term: Term, version: Version, duration: DispatchTimeInterval) {
6162
self.log("resolved: \(term.node.package.location) @ \(version)")
6263
}
6364

@@ -85,10 +86,10 @@ public struct TracingDependencyResolverDelegate: DependencyResolverDelegate {
8586
log("CR: new incompatibility \(incompatibility)")
8687
}
8788

88-
public func computed(bindings: [DependencyResolver.Binding]) {
89+
public func solved(result: [DependencyResolver.Binding]) {
8990
self.log("solved:")
90-
for (container, binding, _) in bindings {
91-
self.log("\(container) \(binding)")
91+
for (package, binding, _) in result {
92+
self.log("\(package) \(binding)")
9293
}
9394
}
9495

@@ -97,3 +98,44 @@ public struct TracingDependencyResolverDelegate: DependencyResolverDelegate {
9798
self.stream.flush()
9899
}
99100
}
101+
102+
public struct MultiplexResolverDelegate: DependencyResolverDelegate {
103+
private let underlying: [DependencyResolverDelegate]
104+
105+
public init (_ underlying: [DependencyResolverDelegate]) {
106+
self.underlying = underlying
107+
}
108+
109+
public func willResolve(term: Term) {
110+
underlying.forEach { $0.willResolve(term: term) }
111+
}
112+
113+
public func didResolve(term: Term, version: Version, duration: DispatchTimeInterval) {
114+
underlying.forEach { $0.didResolve(term: term, version: version, duration: duration) }
115+
}
116+
117+
public func derived(term: Term) {
118+
underlying.forEach { $0.derived(term: term) }
119+
}
120+
121+
public func conflict(conflict: Incompatibility) {
122+
underlying.forEach { $0.conflict(conflict: conflict) }
123+
}
124+
125+
public func satisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility) {
126+
underlying.forEach { $0.satisfied(term: term, by: assignment, incompatibility: incompatibility) }
127+
}
128+
129+
public func partiallySatisfied(term: Term, by assignment: Assignment, incompatibility: Incompatibility, difference: Term) {
130+
underlying.forEach { $0.partiallySatisfied(term: term, by: assignment, incompatibility: incompatibility, difference: difference) }
131+
}
132+
133+
public func failedToResolve(incompatibility: Incompatibility) {
134+
underlying.forEach { $0.failedToResolve(incompatibility: incompatibility) }
135+
}
136+
137+
public func solved(result: [(package: PackageReference, binding: BoundVersion, products: ProductFilter)]) {
138+
underlying.forEach { $0.solved(result: result) }
139+
}
140+
141+
}

Sources/PackageGraph/Pubgrub/PubgrubDependencyResolver.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public struct PubgrubDependencyResolver {
238238
finalAssignments.append((identifier, override.version, override.products))
239239
}
240240

241-
self.delegate?.computed(bindings: finalAssignments)
241+
self.delegate?.solved(result: finalAssignments)
242242

243243
return (finalAssignments, state)
244244
}
@@ -632,6 +632,7 @@ public struct PubgrubDependencyResolver {
632632
// get conflicts (if any) sooner.
633633
self.computeCounts(for: undecided) { result in
634634
do {
635+
let start = DispatchTime.now()
635636
let counts = try result.get()
636637
// forced unwraps safe since we are testing for count and errors above
637638
let pkgTerm = undecided.min { counts[$0]! < counts[$1]! }!
@@ -670,7 +671,7 @@ public struct PubgrubDependencyResolver {
670671

671672
// Decide this version if there was no conflict with its dependencies.
672673
if !haveConflict {
673-
self.delegate?.didResolve(term: pkgTerm, version: version)
674+
self.delegate?.didResolve(term: pkgTerm, version: version, duration: start.distance(to: .now()))
674675
state.decide(pkgTerm.node, at: version)
675676
}
676677

Sources/SPMTestSupport/MockWorkspace.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,10 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate {
592592
self.append("updating repo: \(repository)")
593593
}
594594

595+
public func repositoryDidUpdate(_ repository: String, duration: DispatchTimeInterval) {
596+
self.append("finished updating repo: \(repository)")
597+
}
598+
595599
public func dependenciesUpToDate() {
596600
self.append("Everything is already up-to-date")
597601
}
@@ -600,18 +604,26 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate {
600604
self.append("fetching repo: \(repository)")
601605
}
602606

603-
public func fetchingDidFinish(repository: String, fetchDetails: RepositoryManager.FetchDetails?, diagnostic: Diagnostic?) {
607+
public func fetchingDidFinish(repository: String, fetchDetails: RepositoryManager.FetchDetails?, diagnostic: Diagnostic?, duration: DispatchTimeInterval) {
604608
self.append("finished fetching repo: \(repository)")
605609
}
606610

607611
public func willCreateWorkingCopy(repository url: String, at path: AbsolutePath) {
608612
self.append("creating working copy for: \(url)")
609613
}
610614

615+
public func didCreateWorkingCopy(repository url: String, at path: AbsolutePath, error: Diagnostic?) {
616+
self.append("finished creating working copy for: \(url)")
617+
}
618+
611619
public func willCheckOut(repository url: String, revision: String, at path: AbsolutePath) {
612620
self.append("checking out repo: \(url)")
613621
}
614622

623+
public func didCheckOut(repository url: String, revision: String, at path: AbsolutePath, error: Diagnostic?) {
624+
self.append("finsihed checking out repo: \(url)")
625+
}
626+
615627
public func removing(repository: String) {
616628
self.append("removing repo: \(repository)")
617629
}
@@ -628,6 +640,26 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate {
628640
self.append("did load manifest for \(packageKind) package: \(url)")
629641
}
630642

643+
public func willComputeVersion(package: PackageIdentity, location: String) {
644+
// noop
645+
}
646+
647+
public func didComputeVersion(package: PackageIdentity, location: String, version: String, duration: DispatchTimeInterval) {
648+
// noop
649+
}
650+
651+
public func resolvedFileChanged() {
652+
// noop
653+
}
654+
655+
public func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) {
656+
// noop
657+
}
658+
659+
public func didDownloadBinaryArtifacts() {
660+
// noop
661+
}
662+
631663
private func append(_ event: String) {
632664
self.lock.withLock {
633665
self._events.append(event)

0 commit comments

Comments
 (0)