Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow tools-versions to be specified later in the manifest #7201

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Sources/PackageLoading/ToolsVersionParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ public struct ToolsVersionParser {
}

public static func parse(utf8String: String) throws -> ToolsVersion {
do {
return try Self._parse(utf8String: utf8String)
} catch {
// Keep scanning in case the tools-version is specified somewhere further down in the file.
var string = utf8String
while let newlineIndex = string.firstIndex(where: { $0.isNewline }) {
string = String(string[newlineIndex...].dropFirst())
if !string.isEmpty, let result = try? Self._parse(utf8String: string) {
if result >= ToolsVersion.v5_11 {
return result
} else {
throw Error.backwardIncompatiblePre5_11(.toolsVersionNeedsToBeFirstLine, specifiedVersion: result)
}
}
}
// If we fail to find a tools-version in the entire manifest, throw the original error.
throw error
}
}

private static func _parse(utf8String: String) throws -> ToolsVersion {
assert(!utf8String.isEmpty, "empty manifest should've been diagnosed before parsing the tools version specification")
/// The manifest represented in its constituent parts.
let manifestComponents = Self.split(utf8String)
Expand Down Expand Up @@ -483,6 +504,12 @@ extension ToolsVersionParser {
case unidentified
}

/// Details of backward-incompatible contents with Swift tools version < 5.11.
public enum BackwardIncompatibilityPre5_11 {
/// Tools-versions on subsequent lines of the manifest are only accepted by 5.11 or later.
case toolsVersionNeedsToBeFirstLine
}

/// Package directory is inaccessible (missing, unreadable, etc).
case inaccessiblePackage(path: AbsolutePath, reason: String)
/// Package manifest file is inaccessible (missing, unreadable, etc).
Expand All @@ -493,6 +520,8 @@ extension ToolsVersionParser {
case malformedToolsVersionSpecification(_ malformationLocation: ToolsVersionSpecificationMalformationLocation)
/// Backward-incompatible contents with Swift tools version < 5.4.
case backwardIncompatiblePre5_4(_ incompatibility: BackwardIncompatibilityPre5_4, specifiedVersion: ToolsVersion)
/// Backward-incompatible contents with Swift tools version < 5.11.
case backwardIncompatiblePre5_11(_ incompatibility: BackwardIncompatibilityPre5_11, specifiedVersion: ToolsVersion)

public var description: String {

Expand Down Expand Up @@ -559,6 +588,11 @@ extension ToolsVersionParser {
case .unidentified:
return "the manifest is backward-incompatible with Swift < 5.4, but the package manager is unable to pinpoint the exact incompatibility; consider replacing the current Swift tools version specification with '\(specifiedVersion.specification())' to specify Swift \(specifiedVersion) as the lowest Swift version supported by the project, then move the new specification to the very beginning of this manifest file; additionally, please consider filing a bug report on https://bugs.swift.org with this file attached"
}
case let .backwardIncompatiblePre5_11(incompatibility, _):
switch incompatibility {
case .toolsVersionNeedsToBeFirstLine:
return "the manifest is backward-incompatible with Swift < 5.11 because the tools-version was specified in a subsequent line of the manifest, not the first line. Either move the tools-version specification or increase the required tools-version of your manifest"
}
}

}
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/ToolsVersion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable {
public static let v5_8 = ToolsVersion(version: "5.8.0")
public static let v5_9 = ToolsVersion(version: "5.9.0")
public static let v5_10 = ToolsVersion(version: "5.10.0")
public static let v5_11 = ToolsVersion(version: "5.11.0")
public static let vNext = ToolsVersion(version: "999.0.0")

/// The current tools version in use.
Expand Down
61 changes: 61 additions & 0 deletions Tests/PackageLoadingTests/ToolsVersionParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,67 @@ class ToolsVersionParserTests: XCTestCase {
}
}

func testToolsVersionAllowsComments() throws {
try self.parse(
"""
// comment 1
// comment 2
// swift-tools-version: 5.11
// comment
let package = ..
"""
) { toolsVersion in
XCTAssertEqual(toolsVersion.description, "5.11.0")
}

do {
try self.parse(
"""
// comment 1
// comment 2
// swift-tools-version:5.0
// comment
let package = ..
"""
) { _ in
XCTFail("expected an error to be thrown")
}
} catch ToolsVersionParser.Error.backwardIncompatiblePre5_11(let incompatibility, _) {
XCTAssertEqual(incompatibility, .toolsVersionNeedsToBeFirstLine)
} catch {
XCTFail("unexpected error: \(error)")
}

do {
try self.parse(
"""
// comment 1
// comment 2
let package = ..
"""
) { _ in
XCTFail("expected an error to be thrown")
}
} catch ToolsVersionParser.Error.malformedToolsVersionSpecification(.label(.isMisspelt(let label))) {
XCTAssertEqual(label, "comment")
} catch {
XCTFail("unexpected error: \(error)")
}

try self.parse(
"""
/*
this is a multiline comment
*/
// swift-tools-version: 5.11
// comment
let package = ..
"""
) { toolsVersion in
XCTAssertEqual(toolsVersion.description, "5.11.0")
}
}

/// Verifies that if a manifest appears empty to SwiftPM, a distinct error is thrown.
func testEmptyManifest() throws {
let fs = InMemoryFileSystem()
Expand Down