Skip to content

Commit e2084ae

Browse files
committed
[PackageGraph] Diagnose invalid tools version for revision-based dependencies
<rdar://problem/48980120>
1 parent 8d01aec commit e2084ae

File tree

5 files changed

+154
-73
lines changed

5 files changed

+154
-73
lines changed

Sources/PackageGraph/RepositoryPackageContainerProvider.swift

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -266,22 +266,12 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve
266266
}
267267

268268
// Otherwise, compute and cache the result.
269-
let isValid = (try? self.toolsVersion(for: $0)).flatMap({
270-
guard $0 >= ToolsVersion.minimumRequired else {
271-
return false
272-
}
273-
274-
guard self.currentToolsVersion >= $0 else {
275-
return false
276-
}
277-
278-
return true
279-
}) ?? false
269+
let isValid = (try? self.toolsVersion(for: $0)).flatMap(self.isValidToolsVersion(_:)) ?? false
280270
self.validToolsVersionsCache[$0] = isValid
281-
282271
return isValid
283272
}))
284273
}
274+
285275
/// The opened repository.
286276
let repository: Repository
287277

@@ -403,13 +393,19 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve
403393
) throws -> (Manifest, [RepositoryPackageConstraint]) {
404394
let fs = try repository.openFileView(revision: revision)
405395

396+
let packageURL = identifier.repository.url
397+
406398
// Load the tools version.
407399
let toolsVersion = try toolsVersionLoader.load(at: .root, fileSystem: fs)
408400

401+
// Validate the tools version.
402+
try toolsVersion.validateToolsVersion(
403+
self.currentToolsVersion, version: revision.identifier, packagePath: packageURL)
404+
409405
// Load the manifest.
410406
let manifest = try manifestLoader.load(
411407
package: AbsolutePath.root,
412-
baseURL: identifier.repository.url,
408+
baseURL: packageURL,
413409
version: version,
414410
manifestVersion: toolsVersion.manifestVersion,
415411
fileSystem: fs)
@@ -443,4 +439,15 @@ public class RepositoryPackageContainer: BasePackageContainer, CustomStringConve
443439
}
444440
return self.identifier.with(newName: manifest.name)
445441
}
442+
443+
/// Returns true if the tools version is valid and can be used by this
444+
/// version of the package manager.
445+
private func isValidToolsVersion(_ toolsVersion: ToolsVersion) -> Bool {
446+
do {
447+
try toolsVersion.validateToolsVersion(currentToolsVersion, packagePath: "")
448+
return true
449+
} catch {
450+
return false
451+
}
452+
}
446453
}

Sources/PackageModel/ToolsVersion.swift

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,34 @@ public struct ToolsVersion: CustomStringConvertible, Comparable, Hashable {
9393
public static func < (lhs: ToolsVersion, rhs: ToolsVersion) -> Bool {
9494
return lhs._version < rhs._version
9595
}
96+
97+
/// Returns true if the tools version is valid and can be used by this
98+
/// version of the package manager.
99+
public func validateToolsVersion(
100+
_ currentToolsVersion: ToolsVersion,
101+
version: String? = nil,
102+
packagePath: String
103+
) throws {
104+
// Make sure the package has the right minimum tools version.
105+
guard self >= .minimumRequired else {
106+
throw UnsupportedToolsVersion(
107+
packagePath: packagePath,
108+
version: version,
109+
minimumRequiredToolsVersion: .minimumRequired,
110+
packageToolsVersion: self
111+
)
112+
}
113+
114+
// Make sure the package isn't newer than the current tools version.
115+
guard currentToolsVersion >= self else {
116+
throw RequireNewerTools(
117+
packagePath: packagePath,
118+
version: version,
119+
installedToolsVersion: currentToolsVersion,
120+
packageToolsVersion: self
121+
)
122+
}
123+
}
96124
}
97125

98126
/// Represents a Swift language version.
@@ -191,3 +219,92 @@ extension SwiftLanguageVersion: Codable {
191219
self.init(uncheckedString: rawValue)
192220
}
193221
}
222+
223+
// MARK:- Diagnostics
224+
225+
/// The diagnostic triggered when the package has a newer tools version than the installed tools.
226+
public struct RequireNewerTools: DiagnosticData, Swift.Error {
227+
public static var id = DiagnosticID(
228+
type: RequireNewerTools.self,
229+
name: "org.swift.diags.workspace.\(RequireNewerTools.self)",
230+
description: {
231+
$0 <<< .substitution({
232+
let `self` = $0 as! RequireNewerTools
233+
var text = "package at '\(self.packagePath)'"
234+
if let version = self.version {
235+
text += " @ \(version)"
236+
}
237+
return text
238+
}, preference: .default)
239+
$0 <<< "is using Swift tools version" <<< { $0.packageToolsVersion.description }
240+
$0 <<< "but the installed version is" <<< { "\($0.installedToolsVersion.description)" }
241+
})
242+
243+
/// The path of the package.
244+
public let packagePath: String
245+
246+
/// The version of the package.
247+
public let version: String?
248+
249+
/// The installed tools version.
250+
public let installedToolsVersion: ToolsVersion
251+
252+
/// The tools version of the package.
253+
public let packageToolsVersion: ToolsVersion
254+
255+
public init(
256+
packagePath: String,
257+
version: String? = nil,
258+
installedToolsVersion: ToolsVersion,
259+
packageToolsVersion: ToolsVersion
260+
) {
261+
self.packagePath = packagePath
262+
self.version = version
263+
self.installedToolsVersion = installedToolsVersion
264+
self.packageToolsVersion = packageToolsVersion
265+
}
266+
}
267+
268+
/// The diagnostic triggered when the package has an unsupported tools version.
269+
public struct UnsupportedToolsVersion: DiagnosticData, Swift.Error {
270+
public static var id = DiagnosticID(
271+
type: UnsupportedToolsVersion.self,
272+
name: "org.swift.diags.workspace.\(UnsupportedToolsVersion.self)",
273+
description: {
274+
$0 <<< .substitution({
275+
let `self` = $0 as! UnsupportedToolsVersion
276+
var text = "package at '\(self.packagePath)'"
277+
if let version = self.version {
278+
text += " @ \(version)"
279+
}
280+
return text
281+
}, preference: .default)
282+
$0 <<< "is using Swift tools version" <<< { $0.packageToolsVersion.description }
283+
$0 <<< "which is no longer supported; use" <<< { $0.minimumRequiredToolsVersion.description }
284+
$0 <<< "or newer instead"
285+
})
286+
287+
/// The path of the package.
288+
public let packagePath: String
289+
290+
/// The version of the package.
291+
public let version: String?
292+
293+
/// The tools version required by the package.
294+
public let minimumRequiredToolsVersion: ToolsVersion
295+
296+
/// The current tools version.
297+
public let packageToolsVersion: ToolsVersion
298+
299+
public init(
300+
packagePath: String,
301+
version: String? = nil,
302+
minimumRequiredToolsVersion: ToolsVersion,
303+
packageToolsVersion: ToolsVersion
304+
) {
305+
self.packagePath = packagePath
306+
self.version = version
307+
self.minimumRequiredToolsVersion = minimumRequiredToolsVersion
308+
self.packageToolsVersion = packageToolsVersion
309+
}
310+
}

Sources/Workspace/Diagnostics.swift

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -255,49 +255,6 @@ public enum WorkspaceDiagnostics {
255255
public let revision: String
256256
}
257257

258-
/// The diagnostic triggered when the root package has a newer tools version than the installed tools.
259-
public struct RequireNewerTools: DiagnosticData, Swift.Error {
260-
public static var id = DiagnosticID(
261-
type: RequireNewerTools.self,
262-
name: "org.swift.diags.workspace.\(RequireNewerTools.self)",
263-
description: {
264-
$0 <<< "package at" <<< { "'\($0.packagePath.pathString)'" }
265-
$0 <<< "is using Swift tools version" <<< { $0.packageToolsVersion.description }
266-
$0 <<< "but the installed version is" <<< { "\($0.installedToolsVersion.description)" }
267-
})
268-
269-
/// The path of the package.
270-
public let packagePath: AbsolutePath
271-
272-
/// The installed tools version.
273-
public let installedToolsVersion: ToolsVersion
274-
275-
/// The tools version of the package.
276-
public let packageToolsVersion: ToolsVersion
277-
}
278-
279-
/// The diagnostic triggered when the root package has an unsupported tools version.
280-
public struct UnsupportedToolsVersion: DiagnosticData, Swift.Error {
281-
public static var id = DiagnosticID(
282-
type: UnsupportedToolsVersion.self,
283-
name: "org.swift.diags.workspace.\(UnsupportedToolsVersion.self)",
284-
description: {
285-
$0 <<< "package at" <<< { "'\($0.packagePath)'" }
286-
$0 <<< "is using Swift tools version" <<< { $0.packageToolsVersion.description }
287-
$0 <<< "which is no longer supported; use" <<< { $0.minimumRequiredToolsVersion.description }
288-
$0 <<< "or newer instead"
289-
})
290-
291-
/// The path of the package.
292-
public let packagePath: AbsolutePath
293-
294-
/// The tools version required by the package.
295-
public let minimumRequiredToolsVersion: ToolsVersion
296-
297-
/// The current tools version.
298-
public let packageToolsVersion: ToolsVersion
299-
}
300-
301258
/// The diagnostic triggered when the package at the edit destination is not the
302259
/// one user is trying to edit.
303260
public struct MismatchingDestinationPackage: DiagnosticData, Swift.Error {

Sources/Workspace/Workspace.swift

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,23 +1031,9 @@ extension Workspace {
10311031
let toolsVersion = try toolsVersionLoader.load(
10321032
at: packagePath, fileSystem: fileSystem)
10331033

1034-
// Make sure the package has the right minimum tools version.
1035-
guard toolsVersion >= ToolsVersion.minimumRequired else {
1036-
throw WorkspaceDiagnostics.UnsupportedToolsVersion(
1037-
packagePath: packagePath,
1038-
minimumRequiredToolsVersion: .minimumRequired,
1039-
packageToolsVersion: toolsVersion
1040-
)
1041-
}
1042-
1043-
// Make sure the package isn't newer than the current tools version.
1044-
guard currentToolsVersion >= toolsVersion else {
1045-
throw WorkspaceDiagnostics.RequireNewerTools(
1046-
packagePath: packagePath,
1047-
installedToolsVersion: currentToolsVersion,
1048-
packageToolsVersion: toolsVersion
1049-
)
1050-
}
1034+
// Validate the tools version.
1035+
try toolsVersion.validateToolsVersion(
1036+
currentToolsVersion, packagePath: packagePath.pathString)
10511037

10521038
// Load the manifest.
10531039
// FIXME: We should have a cache for this.

Tests/PackageGraphTests/RepositoryPackageContainerProviderTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,20 @@ class RepositoryPackageContainerProviderTests: XCTestCase {
337337
let v = container.versions(filter: { _ in true }).map{$0}
338338
XCTAssertEqual(v, [])
339339
}
340+
341+
// Test that getting dependencies on a revision that has unsupported tools version is diganosed properly.
342+
do {
343+
let provider = createProvider(ToolsVersion(version: "4.0.0"))
344+
let ref = PackageReference(identity: "foo", path: specifier.url)
345+
let container = try await { provider.getContainer(for: ref, completion: $0) } as! RepositoryPackageContainer
346+
let revision = try container.getRevision(forTag: "1.0.0")
347+
do {
348+
_ = try container.getDependencies(at: revision.identifier)
349+
} catch let error as RepositoryPackageContainer.GetDependenciesErrorWrapper {
350+
let error = error.underlyingError as! UnsupportedToolsVersion
351+
XCTAssertMatch(error.description, .and(.prefix("package at '/some-repo' @"), .suffix("is using Swift tools version 3.1.0 which is no longer supported; use 4.0.0 or newer instead")))
352+
}
353+
}
340354
}
341355

342356
func testPrereleaseVersions() throws {

0 commit comments

Comments
 (0)