Skip to content

Commit b699e07

Browse files
authored
improve "exhausted attempts" diagnostics (#6956)
motivation: better diagnostics when depdendcy graph fails to resolve. the "exhausted attempts" error message should normally not be encountered, other than in edge cases where the graph is not resolvable. nevertheless, it should be readable by users changes: * conslidate the formatting of the "exhausted attempts" error message * format the "exhausted attempts" more clearly so it is more readable * update test rdar://112134269
1 parent d5f6b45 commit b699e07

File tree

3 files changed

+56
-23
lines changed

3 files changed

+56
-23
lines changed

Sources/Workspace/Diagnostics.swift

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ public struct ManifestParseDiagnostic: CustomStringConvertible {
2828
}
2929

3030
public var description: String {
31-
"manifest parse error(s):\n" + errors.joined(separator: "\n")
31+
"manifest parse error(s):\n" + self.errors.joined(separator: "\n")
3232
}
3333
}
3434

3535
public enum WorkspaceDiagnostics {
36-
3736
// MARK: - Errors
3837

3938
/// The diagnostic triggered when an operation fails because its completion
@@ -43,7 +42,7 @@ public enum WorkspaceDiagnostics {
4342
public let repositoryPath: AbsolutePath
4443

4544
public var description: String {
46-
return "repository '\(repositoryPath)' has uncommited changes"
45+
"repository '\(self.repositoryPath)' has uncommited changes"
4746
}
4847
}
4948

@@ -54,7 +53,7 @@ public enum WorkspaceDiagnostics {
5453
public let repositoryPath: AbsolutePath
5554

5655
public var description: String {
57-
return "repository '\(repositoryPath)' has unpushed changes"
56+
"repository '\(self.repositoryPath)' has unpushed changes"
5857
}
5958
}
6059

@@ -65,7 +64,7 @@ public enum WorkspaceDiagnostics {
6564
public let dependencyName: String
6665

6766
public var description: String {
68-
return "dependency '\(dependencyName)' not in edit mode"
67+
"dependency '\(self.dependencyName)' not in edit mode"
6968
}
7069
}
7170

@@ -76,7 +75,7 @@ public enum WorkspaceDiagnostics {
7675
public let branch: String
7776

7877
public var description: String {
79-
return "branch '\(branch)' already exists"
78+
"branch '\(self.branch)' already exists"
8079
}
8180
}
8281

@@ -87,7 +86,7 @@ public enum WorkspaceDiagnostics {
8786
public let revision: String
8887

8988
public var description: String {
90-
return "revision '\(revision)' does not exist"
89+
"revision '\(self.revision)' does not exist"
9190
}
9291
}
9392
}
@@ -98,11 +97,15 @@ extension Basics.Diagnostic {
9897
}
9998

10099
static func editBranchNotCheckedOut(packageName: String, branchName: String) -> Self {
101-
.warning("dependency '\(packageName)' already exists at the edit destination; not checking-out branch '\(branchName)'")
100+
.warning(
101+
"dependency '\(packageName)' already exists at the edit destination; not checking-out branch '\(branchName)'"
102+
)
102103
}
103104

104105
static func editRevisionNotUsed(packageName: String, revisionIdentifier: String) -> Self {
105-
.warning("dependency '\(packageName)' already exists at the edit destination; not using revision '\(revisionIdentifier)'")
106+
.warning(
107+
"dependency '\(packageName)' already exists at the edit destination; not using revision '\(revisionIdentifier)'"
108+
)
106109
}
107110

108111
static func editedDependencyMissing(packageName: String) -> Self {
@@ -122,35 +125,49 @@ extension Basics.Diagnostic {
122125
}
123126

124127
static func artifactInvalidArchive(artifactURL: URL, targetName: String) -> Self {
125-
.error("invalid archive returned from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)'")
128+
.error(
129+
"invalid archive returned from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)'"
130+
)
126131
}
127132

128133
static func artifactChecksumChanged(targetName: String) -> Self {
129-
.error("artifact of binary target '\(targetName)' has changed checksum; this is a potential security risk so the new artifact won't be downloaded")
134+
.error(
135+
"artifact of binary target '\(targetName)' has changed checksum; this is a potential security risk so the new artifact won't be downloaded"
136+
)
130137
}
131138

132139
static func artifactInvalidChecksum(targetName: String, expectedChecksum: String, actualChecksum: String?) -> Self {
133-
.error("checksum of downloaded artifact of binary target '\(targetName)' (\(actualChecksum ?? "none")) does not match checksum specified by the manifest (\(expectedChecksum))")
140+
.error(
141+
"checksum of downloaded artifact of binary target '\(targetName)' (\(actualChecksum ?? "none")) does not match checksum specified by the manifest (\(expectedChecksum))"
142+
)
134143
}
135144

136145
static func artifactFailedDownload(artifactURL: URL, targetName: String, reason: String) -> Self {
137-
.error("failed downloading '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)")
146+
.error(
147+
"failed downloading '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)"
148+
)
138149
}
139150

140151
static func artifactFailedValidation(artifactURL: URL, targetName: String, reason: String) -> Self {
141-
.error("failed validating archive from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)")
152+
.error(
153+
"failed validating archive from '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)"
154+
)
142155
}
143156

144157
static func remoteArtifactFailedExtraction(artifactURL: URL, targetName: String, reason: String) -> Self {
145-
.error("failed extracting '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)")
158+
.error(
159+
"failed extracting '\(artifactURL.absoluteString)' which is required by binary target '\(targetName)': \(reason)"
160+
)
146161
}
147162

148163
static func localArtifactFailedExtraction(artifactPath: AbsolutePath, targetName: String, reason: String) -> Self {
149164
.error("failed extracting '\(artifactPath)' which is required by binary target '\(targetName)': \(reason)")
150165
}
151166

152167
static func remoteArtifactNotFound(artifactURL: URL, targetName: String) -> Self {
153-
.error("downloaded archive of binary target '\(targetName)' from '\(artifactURL.absoluteString)' does not contain a binary artifact.")
168+
.error(
169+
"downloaded archive of binary target '\(targetName)' from '\(artifactURL.absoluteString)' does not contain a binary artifact."
170+
)
154171
}
155172

156173
static func localArchivedArtifactNotFound(archivePath: AbsolutePath, targetName: String) -> Self {
@@ -160,11 +177,25 @@ extension Basics.Diagnostic {
160177
static func localArtifactNotFound(artifactPath: AbsolutePath, targetName: String) -> Self {
161178
.error("local binary target '\(targetName)' at '\(artifactPath)' does not contain a binary artifact.")
162179
}
163-
}
164180

181+
static func exhaustedAttempts(missing: [PackageReference]) -> Self {
182+
let missing = missing.sorted(by: { $0.identity < $1.identity }).map {
183+
switch $0.kind {
184+
case .registry(let identity):
185+
return "'\(identity.description)'"
186+
case .remoteSourceControl(let url):
187+
return "'\($0.identity)' from \(url)"
188+
case .localSourceControl(let path), .fileSystem(let path), .root(let path):
189+
return "'\($0.identity)' at \(path)"
190+
}
191+
}
192+
return .error(
193+
"exhausted attempts to resolve the dependencies graph, with the following dependencies unresolved:\n* \(missing.joined(separator: "\n* "))"
194+
)
195+
}
196+
}
165197

166198
extension FileSystemError: CustomStringConvertible {
167-
168199
public var description: String {
169200
guard let path else {
170201
switch self.kind {

Sources/Workspace/Workspace.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,8 +1180,7 @@ extension Workspace {
11801180
// If we have missing packages, something is fundamentally wrong with the resolution of the graph
11811181
let stillMissingPackages = try updatedDependencyManifests.missingPackages
11821182
guard stillMissingPackages.isEmpty else {
1183-
let missing = stillMissingPackages.map{ $0.description }
1184-
observabilityScope.emit(error: "exhausted attempts to resolve the dependencies graph, with '\(missing.sorted().joined(separator: "', '"))' unresolved.")
1183+
observabilityScope.emit(.exhaustedAttempts(missing: stillMissingPackages))
11851184
return nil
11861185
}
11871186

@@ -2970,8 +2969,7 @@ extension Workspace {
29702969
// If we still have missing packages, something is fundamentally wrong with the resolution of the graph
29712970
let stillMissingPackages = try updatedDependencyManifests.missingPackages
29722971
guard stillMissingPackages.isEmpty else {
2973-
let missing = stillMissingPackages.map{ $0.description }
2974-
observabilityScope.emit(error: "exhausted attempts to resolve the dependencies graph, with '\(missing.sorted().joined(separator: "', '"))' unresolved.")
2972+
observabilityScope.emit(.exhaustedAttempts(missing: stillMissingPackages))
29752973
return updatedDependencyManifests
29762974
}
29772975

Tests/WorkspaceTests/WorkspaceTests.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11269,7 +11269,11 @@ final class WorkspaceTests: XCTestCase {
1126911269
severity: .error
1127011270
)
1127111271
result.check(
11272-
diagnostic: "exhausted attempts to resolve the dependencies graph, with 'bar remoteSourceControl http://scm.com/org/bar', 'foo remoteSourceControl http://scm.com/org/foo' unresolved.",
11272+
diagnostic: """
11273+
exhausted attempts to resolve the dependencies graph, with the following dependencies unresolved:
11274+
* 'bar' from http://scm.com/org/bar
11275+
* 'foo' from http://scm.com/org/foo
11276+
""",
1127311277
severity: .error
1127411278
)
1127511279
}

0 commit comments

Comments
 (0)